How do I await multiple promises in-parallel without 'fail-fast' behavior?

Brandon picture Brandon · Dec 22, 2016 · Viewed 66.8k times · Source

I'm using async/await to fire several api calls in parallel:

async function foo(arr) {
  const results = await Promise.all(arr.map(v => {
     return doAsyncThing(v)
  }))
  return results
}

I know that, unlike loops, Promise.all executes in-parallel (that is, the waiting-for-results portion is in parallel).

But I also know that:

Promise.all is rejected if one of the elements is rejected and Promise.all fails fast: If you have four promises which resolve after a timeout, and one rejects immediately, then Promise.all rejects immediately.

As I read this, if I Promise.all with 5 promises, and the first one to finish returns a reject(), then the other 4 are effectively cancelled and their promised resolve() values are lost.

Is there a third way? Where execution is effectively in-parallel, but a single failure doesn't spoil the whole bunch?

Answer

NoNameProvided picture NoNameProvided · Feb 8, 2017

While the technique in the accepted answer can solve your issue, it's an anti-pattern. Resolving a promise with an error isn't good practice and there is a cleaner way of doing this.

What you want to do, in pseudo-code, is:

fn task() {
  result-1 = doAsync();
  result-n = doAsync();

  // handle results together
  return handleResults(result-1, ..., result-n)
}

This can be achieved simply with async/await without the need to use Promise.all. A working example:

console.clear();

function wait(ms, data) {
  return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}

/** 
 * These will be run in series, because we call
 * a function and immediately wait for each result, 
 * so this will finish in 1s.
 */
async function series() {
  return {
    result1: await wait(500, 'seriesTask1'),
    result2: await wait(500, 'seriesTask2'),
  }
}

/** 
 * While here we call the functions first,
 * then wait for the result later, so 
 * this will finish in 500ms.
 */
async function parallel() {
  const task1 = wait(500, 'parallelTask1');
  const task2 = wait(500, 'parallelTask2');

  return {
    result1: await task1,
    result2: await task2,
  }
}

async function taskRunner(fn, label) {
  const startTime = performance.now();
  console.log(`Task ${label} starting...`);
  let result = await fn();
  console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } miliseconds with,`, result);
}

void taskRunner(series, 'series');
void taskRunner(parallel, 'parallel');

Note: You will need a browser which has async/await enabled to run this snippet.

This way you can use simply try/ catch to handle your errors, and return partial results inside parallel function.