Talha Irfan
Talha Irfan

Reputation: 89

Promise All retry

I know that promise.all() fails when even 1 of the promise is failed. I only want to try for failed promises and don't want to run promise.all() again.

Any recommendations on how I can achieve this in minimal way?

Upvotes: 3

Views: 5750

Answers (2)

num8er
num8er

Reputation: 19372

You may use async package and wrap all promise calls with closure with done argument.

Then simply resolve results.

const async = require('async');

const promiseAll = promises => {
  return new Promise((resolve) => {
    
   // preparing array of functions which has done method as callback
   const parallelCalls = promises.map(promise => {
      return done => {
        promise
          .then(result => done(null, result)
          .catch(error => {
            console.error(error.message);
            done();
          });
      }
    });
    
    // calling array of functions in parallel
    async.parallel(
      parallelCalls, 
      (_, results) => resolve(results.filter(Boolean))
    );

  });
};


router.get('/something', async (req, res) => {
  ...
  const results = await promiseAll(promises);
  ...
});

Or we can simply do Promise.all without using async package:

router.get('/something', async (req, res) => {
  ...
  const results = (
    await Promise.all(
      promises.map(promise => {
        return new Promise(resolve => {
          promise.then(resolve).catch(e => resolve());
        });
      });
    )
  ).filter(Boolean);
  ...
});

Upvotes: 2

adz5A
adz5A

Reputation: 2032

Promises are eager construct and model a value obtained asynchronously, a Promise is produced using some kind of producer, like fetch for instance.

If you retain a reference to this producer then you can replay the nmechanism that produced the Promise in the first place.

// producer function
function getData (arg) {
  const result = new Promise();
  return result.then(value => {
    return { ok:true, value };
  }, error => {
    return {
      ok: false,
      value: error,
      // retry is a function which calls the producer with the same arguments
      retry: () => getData(arg)
    };
  })

}

Then if you have something like:

const data = [];

// Promise<{ok: boolean, value: any, retry?: function}>
// No promises will fail in this array
const asyncResults = data.map(getResults); 


Promise.all(asyncResults)
  .then((results) => {
    const successes = results.filter(res => res.ok);
    const retrys = results.filter(res => !res.ok).map(res => res.retry()); // retry all failed promises
  })

Memory leaks, stack overflow: because I retain a reference to original arguments in order to retry and the algorithm is recursive there could be a memory leak. However the algorithm cannot "stack overflow":

  • getData calls do not get "deeper" over time (see retry definition)
  • the asyncrhonicity of the algorithm prevent this behaviour if a promise was never resolved
  • old data is properly discarded when accessing the results as const resultData = results.filter(res => res.ok).map(res => res.value);

However the algorithm could take a long time to settle if a promise keep on not getting resolved and prevent access to the rest of the values.

In an alternative I suggest you take a look at another async primitive, not yet part of the language (maybe some day) : Observables which are designed for this kind of tasks: lazy, retry-able async operations.

Upvotes: 3

Related Questions