【问题标题】:Why does Promise.race() fulfill with an unfulfilled promise?为什么 Promise.race() 会履行未履行的承诺?
【发布时间】:2022-01-18 04:38:59
【问题描述】:

我在 Puppeteer 的页面上运行一系列函数。如果其中一个函数抛出,我会重试整个序列几次,因为我使用的系统不可靠。这比处理所有可能的异常要容易。

有时,其中一个步骤会挂起,而不是抛出,所以我想为了安全起见超时,然后像发生异常一样重试。

如果我不将我的序列包装在下面的asyncCallWithTimeout() 中,一切都会正常工作并解析为我想要的(一个数组)。但是,当我使用该函数时,我留下了一个未解析的函数,我将其传递为 asyncPromise

在我的一生中,我无法弄清楚为什么 Promise.race() 会以我通过的未解决 asyncPromise 解决。我错过了什么?

这是在其他异步函数运行时间过长时使它们超时的函数:

async function asyncCallWithTimeout(asyncPromise, timeLimit) {
  let timeoutHandle;

  const timeoutPromise = new Promise((_resolve, reject) => {
    timeoutHandle = setTimeout(
      () => reject(new Error('Async call timeout limit reached')),
      timeLimit
    );
  });

  return Promise.race([asyncPromise, timeoutPromise]).then((result) => {
    clearTimeout(timeoutHandle); // .then() looks to be executed every time even if timeout is set to 1 ms
    return result;
  });
};

const timer = ms => new Promise(r => setTimeout(r, ms));

const shouldResolve = asyncCallWithTimeout(timer(1000), 2000).then(console.log).catch(console.error);
const shouldReject = asyncCallWithTimeout(timer(2000), 1000).then(console.log).catch(console.error);

如果相关,这里是重试函数:

async function retry(promiseFactory, retryCount) {
    try {
      return await promiseFactory();
    } catch (error) {
      if (retryCount <= 0) {
        throw error;
      }
      return await retry(promiseFactory, retryCount - 1);
    }
};

我是这样用的:

async function checkT(browser, url) {
    const tUrl = `https://example.com/?url=${url}`;
    return retry(async () => {
        const browserPage = await browser.newPage();
        try {
            return asyncCallWithTimeout(async () => { // works if this is commented
                await browserPage.goto(tUrl);
                await doSomething(browserPage);
                await waitForSomething(browserPage);
                const results = await getResults(browserPage);
                await browserPage.close();
                return results;
            }, 10000); // works if this is commented
        } catch (error) {
            await browserPage.close();
            throw error;
        }
    }, 3);
};

然后我在其他地方这样称呼它(使用p-queue,这是无关紧要的):

for (const page of pages) {
    queue.add(() => checkT(browser, page.url)
        .then(async (results) => {
            // results should be an array but is a function and not iterable hence fails below
            for (const result of results.data) {
                // do something with the result
            }
        })
    );
};

【问题讨论】:

  • 我添加了一个可运行的 sn-p 来测试您的问题,但它似乎工作正常。具有较小超时的承诺是两个示例中的最终值。您可以修改该 sn-p 以重现您的问题吗?
  • 你的asyncCallWithTimeout 期望得到一个承诺,但你却传递了一个async function。你也错过了try 块中的await,通过立即returning 承诺拒绝不会导致catch 子句运行。顺便说一句,我建议无论如何都使用finally 作为browserPage.close()
  • 谢谢@Bergi。你能解释一下丢失的await 以及为什么直接的return 永远不会触发catch?我很想阅读更多内容,但不知道要搜索什么。关于finally 我想这只是你的个人喜好?
  • 请参阅 herethere 了解 return await。至于finally,不,客观上更好。它更清楚地表达了您的意图,避免重复清理代码,并且如果在await browserPage.close();期间发生超时或其中发生错误,它不会再次尝试关闭浏览器页面。

标签: javascript async-await promise puppeteer


【解决方案1】:

您将一个函数作为asyncCallWithTimeout 的第一个参数传递,但我假设您想在给定参数名称和使用方式的情况下传递一个promise。

当你调用 Promise.race 时,asyncPromise 仍然是一个函数:

Promise.race([asyncPromise, timeoutPromise])

要让它工作,你应该调用它:

            return asyncCallWithTimeout((async () => {
                await browserPage.goto(tUrl);
                await doSomething(browserPage);
                await waitForSomething(browserPage);
                const results = await getResults(browserPage);
                await browserPage.close();
                return results;
            })(), 10000);
//            ^
//            |
// You arrow function called here, 
// so this should return a promise 
// to the first argument of `asyncCallWithTimeout`

请注意,该函数被包裹在括号中,然后在这里调用。显然,这不是调用函数的唯一方法,你可以给它一个名字并调用它,然后传入返回的 promise 变量,但我希望你明白我的意思。

【讨论】:

  • 是的,就是这样!谢谢,您能否指出一些我可以阅读的文档以了解它为什么不执行?我认为默认情况下异步函数会包装在承诺中,但现在我看到它是包装在承诺中的返回值。
猜你喜欢
  • 1970-01-01
  • 2014-06-04
  • 2017-10-06
  • 1970-01-01
  • 2015-08-24
  • 2016-11-07
  • 2015-11-16
  • 2015-11-17
相关资源
最近更新 更多