Promise.allSettled() - 多个异步调用的重试策略

Promise.allSettled() - Retry strategy for multiple async calls

TL;DR:我正在寻找处理未知数量的承诺拒绝并在使用 Promise.allSettled() 进行多个异步调用时重试 X 时间量的策略或示例.

阅读本文后:https://www.coreycleary.me/better-handling-of-rejections-using-promise-allsettled 这句话让我很兴奋:

This is great because we can still load the user account info, and retry fetching the user activity later. (retries are outside the scope of this post, and there are multiple strategies for that)

然而,在网上搜索时,我绝对找到了none具体的例子,甚至是直接处理这个问题的帖子。

下面是一些示例代码:

  public async test() {
    try {
      console.log('trying..');
      const multiAsync = [this.somethingA(), this.somethingB(), this.somethingC()];
      const [a, b, c] = await Promise.allSettled(multiAsync);
    } catch (e) {
      console.log('catch');
      console.log(e);
    }
  }

现在假设上面的代码,A 和 C 都失败了,我想重试它们,比方说 - 再试一次。 即使我有a, b, c,我也只知道哪些是fullfilled:true,哪些不是。但我不知道 link a 回到 somethingA()csomethingC() 只重试这两个函数,我绝对不想调用 somethingB() 两次..

有什么想法吗?

Promise 不会保留任何关于它们来自何处的记录:一旦您有了 Promise,就没有任何东西可以让您多次重试它的来源。因此, 接受 promise-returning 函数(“promise 工厂”),而不仅仅是 Promise 本身。 在调用 allallSettled 之前将每个单独的承诺包装在这样的重试函数中是最实用的解决方案,因为快速失败的承诺可以立即重试而无需等待对于整个列表,就像 allSettled 那样。

const multiAsync = [
  retry(() => this.somethingA(), 3),
  retry(() => this.somethingB(), 3),
  retry(() => this.somethingC(), 3),
];
const [a, b, c] = await Promise.allSettled(multiAsync);

// or

const [a, b, c] =
    await Promise.allSettled([/* functions */].map(x => retry(x, 3));

但是,如果您想了解如何使用 Promise.allSettled 直接执行此操作,我这里有一个。我这里的解决方案不考虑超时,您可以全局或单独添加超时 .

/**
 * Resolves a series of promise factories, retrying if needed.
 *
 * @param {number} maxTryCount How many retries to perform.
 * @param {Array<() => Promise<any>>} promiseFactories Functions
 *     that return promises. These must be functions to enable retrying.
 * @return Corresponding Promise.allSettled values.
 */
async function allSettledWithRetry(maxTryCount, promiseFactories) {
  let results;
  for (let retry = 0; retry < maxTryCount; retry++) {
    let promiseArray;
    if (results) {
      // This is a retry; fold in results and new promises.
      promiseArray = results.map(
          (x, index) => x.status === "fulfilled"
            ? x.value
            : promiseFactories[index]())
    } else {
      // This is the first run; use promiseFactories only.
      promiseArray = promiseFactories.map(x => x());
    }
    results = await Promise.allSettled(promiseArray);
    // Avoid unnecessary loops, though they'd be inexpensive.
    if (results.every(x => x.status === "fulfilled")) {
      return results;
    }
  }
  return results;
}

/* test harness below */

function promiseFactory(label) {
  const succeeds = Math.random() > 0.5;
  console.log(`${label}: ${succeeds ? 'succeeds' : 'fails'}`);
  return succeeds
      ? Promise.resolve(label)
      : Promise.reject(new Error(`Error: ${label}`));
}

allSettledWithRetry(5, [
  () => promiseFactory("a"),
  () => promiseFactory("b"),
  () => promiseFactory("c"),
  () => promiseFactory("d"),
  () => promiseFactory("e"),
]).then(console.log);

这对我很有效。

export const allSettledWithRetries = async (
  promises: Promise<FixMeLater>[],
  retries: number,
  waitBetweenRetries = 1000
): Promise<FixMeLater[]> => {
  const results = await Promise.allSettled(promises);

  const successResponses: FixMeLater[] = [];
  results.forEach((promiseResult) => {
    if (promiseResult.status === 'fulfilled') {
      successResponses.push(promiseResult.value);
    }
  });

  if (retries > 0 && successResponses.length !== promises.length) {
    const failedPromises: Promise<FixMeLater>[] = [];

    results.forEach((promiseResult, index) => {
      if (promiseResult.status === 'rejected') {
        failedPromises.push(promises[index]);
      }
    });

    logger.warn(`failed to run ${failedPromises.length} and will try again`);

    // eslint-disable-next-line no-promise-executor-return
    await new Promise((resolve) => setTimeout(resolve, waitBetweenRetries));

    const failedResults = await allSettledWithRetries(
      failedPromises,
      retries - 1
    );

    failedResults.forEach((promiseResult) => {
      if (promiseResult.status === 'fulfilled') {
        successResponses.push(promiseResult.value);
      }
    });
  }

  return successResponses;
};