Home > front end >  Promise.allSettled() - Retry strategy for multiple async calls
Promise.allSettled() - Retry strategy for multiple async calls

Time:02-23

TL;DR: I'm looking for a strategy or an example of handling an unknown amount of promise rejections and retrying them an X amount of time when using Promise.allSettled() for multiple async calls.

After reading this article: https://www.coreycleary.me/better-handling-of-rejections-using-promise-allsettled I got pretty excited by this sentence:

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)

However, when researching online I found absolutely none concrete examples, or even posts that deal with this issue directly.

Here is some example code:

  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);
    }
  }

Now lets say for the above code, both A and C failed, and I want to retry them, let's say- just one more time. Even Though I have a, b, c, I only know which is fullfilled:true and which are not. but I don't know to link a back to somethingA() and c to somethingC() to retry only those 2 functions, and I definitely don't want to invoke somethingB() twice..

Any ideas?

CodePudding user response:

Promises do not keep any record of where they came from: Once you have a Promise, there's nothing that would allow you to retry its origin multiple times. As such, common Promise retry patterns accept the promise-returning function ("promise factory"), not just the Promise itself. Wrapping each individual promise in a retry function like this before calling all or allSettled is the most practical solution, since promises that fail quickly can try again immediately without waiting for the entire list like allSettled does.

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));

However, if you'd like to see how to use Promise.allSettled to do this directly, I have one here. My solution here doesn't respect timeouts, which you can add globally or individually with a Promise.race implementation.

/**
 * 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);

  • Related