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?
You can use the async/await syntax or call the . then() method on a promise to wait for it to resolve. Inside of functions marked with the async keyword, you can use await to wait for the promises to resolve before continuing to the next line of the function.
Approach 1: In this approach, we will use Promise. all() method which takes all promises in a single array as its input. As a result, this method executes all the promises in itself and returns a new single promise in which the values of all the other promises are combined together.
As far as I understand, in ES7/ES2016 putting multiple await 's in code will work similar to chaining . then() with promises, meaning that they will execute one after the other rather than in parallel. So, for example, we have this code: await someCall(); await anotherCall();
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With