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?
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.