为什么 Promise.race() 会履行未履行的承诺?

Why does Promise.race() fulfill with an unfulfilled promise?

我正在 运行在 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
            }
        })
    );
};

你正在传递一个函数作为 asyncCallWithTimeout 的第一个参数,但我假设你想传递一个给定参数名称和你如何使用它的承诺。

当您调用 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 变量,但我希望您能理解我的意思。