How do I await multiple promises in-parallel without 'fail-fast' behavior? [duplicate]
Solution 1:
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.
Solution 2:
ES2020 contains Promise.allSettled, which will do what you want.
Promise.allSettled([
Promise.resolve('a'),
Promise.reject('b')
]).then(console.log)
Output:
[
{
"status": "fulfilled",
"value": "a"
},
{
"status": "rejected",
"reason": "b"
}
]
But if you want to "roll your own", then you can leverage the fact that using Promise#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.
So, by simply handling errors with catch
, you can achieve what you want.
Note that if you want the errors to be visible in the result, you will have to decide on a convention for surfacing them.
You can apply a rejection handling function to each promise in a collection using Array#map, and use Promise.all to wait for all of them to complete.
Example
The following should print out:
Elapsed Time Output
0 started...
1s foo completed
1s bar completed
2s bam errored
2s done [
"foo result",
"bar result",
{
"error": "bam"
}
]
async function foo() {
await new Promise((r)=>setTimeout(r,1000))
console.log('foo completed')
return 'foo result'
}
async function bar() {
await new Promise((r)=>setTimeout(r,1000))
console.log('bar completed')
return 'bar result'
}
async function bam() {
try {
await new Promise((_,reject)=>setTimeout(reject,2000))
} catch {
console.log('bam errored')
throw 'bam'
}
}
function handleRejection(p) {
return p.catch((error)=>({
error
}))
}
function waitForAll(...ps) {
console.log('started...')
return Promise.all(ps.map(handleRejection))
}
waitForAll(foo(), bar(), bam()).then(results=>console.log('done', results))
See also.