This question already has an answer here:
-
Wait until all ES6 promises complete, even rejected promises
13 answers
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?
Using catch
means that the promise resolves (unless you throw an exception from the catch
or manually reject the promise chain), so you do not need to explicitly return a resolved promise IIUC.
This means that simply by handling errors with catch
you can achieve what you want.
If you want to standardize the way rejections are handled then you can apply a rejection handling function to all the promises.
async function bar() {
await new Promise(r=> setTimeout(r, 1000))
.then(()=> console.log('bar'))
.then(()=> 'bar result');
}
async function bam() {
await new Promise((ignore, reject)=> setTimeout(reject, 2000))
.catch(()=> { console.log('bam errored'); throw 'bam'; });
}
async function bat() {
await new Promise(r=> setTimeout(r, 3000))
.then(()=> console.log('bat'))
.then(()=> 'bat result');
}
function handleRejection(p) {
return p.catch(err=> ({ error: err }));
}
async function foo(arr) {
console.log('foo');
return await Promise.all([bar(), bam(), bat()].map(handleRejection));
}
foo().then(results=> console.log('done', results));
While the technique in the accepted answer can solve your issue, it's 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 is in pseudo language 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 of using Promise.all
. A working example:
console.clear();
function wait(ms, data) {
return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}
/**
* This will be runned in series, because
* we call a function and immediately wait for it's 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.