| Short-circuit? | Short-circuits on? | Fulfilled on? | Rejected on? | |
|---|---|---|---|---|
| Promise.all | Yes | First rejected promise | All promise fulfilled | First rejected promise |
| Promise.allSettled | No | N/A | Always | N/A |
| Promise.race | Yes | First settled | First promise fulfilled | First rejected promise |
| Promise.any | Yes | First fulfilled | First promise fulfilled | All rejected promises |
_Photo by Ryan Franco on _Unsplash
What’s new in JavaScript (Google I/O ’19) on May 8, 2019 showed what's coming/available for static Promise combinator methods, Promise.allSettled and Promise.any.
There are already two methods available in modern browsers, Promise.all and Promise.race.
Let's take a look at differences and how each method works.
🚀 Prerequisite
🔆 Promise Definition
I will skip on what a promise is and jump straight into static methods and will discuss differences.
A gist is that, a promise is JavaScript's way of promising you that a work will be done (or might fail if the work could not be completed).
If you are familiar with C#, it's analogous Task class.
For more info, refer to following documentations.
- Promise- JavaScript on MDN
- JavaScript Promises: an Introduction on Google Developers
🔆 Promise State Definitions
- Fulfilled - When a promise is resolved successfully.
- Rejected - When a promise failed.
- Pending - When a promise is "neither fulfilled nor rejected".
- Settled - Not really a state but an umbrella term to describe that a promise is either fulfilled or rejected.
- This term will be used to describe characteristics of new methods later.
For more detailed explanation of states & fates, please refer to States and Fates.
There are other static Promise methods such as Promise.reject, Promise.resolve but I will cover only "combinator" methods, which takes in an iterable object as an argument.
🚀 Differences
Let's first take a look at difference between existing & new combinator methods.
🔅 Promise.all vs. Promise.allSettled
Both accepts an iterable object but
- Promise.all rejects as soon as a promise within the iterable object rejected.
- Promise.allSettled resolves regardless of rejected promise(s) within the iterable object.
🔅 Promise.race vs. Promise.any
Both accepts an iterable object but
- Promise.race short-circuits on the first settled (fulfilled or rejected) promise within the iterable object.
- Promise.any short-circuits on the first fulfilled promise and continues to resolve regardless of rejected promises unless all within the iterable object reject.
🚀 Comparison Table
Now let's take a look at existing/upcoming combinator methods.
Now let's move on to learn more about each method.
Note that all "Characteristics" are taken from TC39 proposal README.
🚀 Promise.all
- What is this? Resolve all promises passed as an iterable object.
- Idiom - One bad 🍏 spoils the bunch ("all").
- Characteristic - short-circuits when an input value is rejected
🔆 Example
| const promisesWithoutReject = [ | |
| Promise.resolve('🍎 #1'), | |
| '🍎 #2', | |
| new Promise((resolve, reject) => setTimeout(resolve, 100, '🍎 #3')) | |
| ] | |
| Promise.all(promisesWithoutReject) | |
| .then(apples => console.log(`We can sell all these good apples`, apples)) | |
| const promisesWithOneReject = [ | |
| Promise.resolve('🍎 #1'), | |
| '🍎 #2', | |
| new Promise((_, reject) => setTimeout(reject, 100, 'Bad 🍏')) | |
| ] | |
| Promise.all(promisesWithOneReject) | |
| .then(console.log) | |
| .catch(badApple => | |
| console.error(`Threw out all apples because of this`, badApple)) |
| We can sell all these good apples [ '🍎 #1', '🍎 #2', '🍎 #3' ] | |
| Threw out all apples because of this Bad 🍏 |
When Promise.all fulfilled(promisesWithoutReject), all apples are returned.
The latter example using promisesWithOneReject shows that one rejected promise results in rejecting all promises.
🚀 Promise.allSettled
- What is this? all promises regardless of settled (fulfilled/rejected) status.
- Idiom - Let's "wait and see" 🤔.
- Characteristic - Does not short-circuit unlike Promise.all/race
- Note - Available in Chrome 76.
🔆 Example
| const allRejectedPromises = [ | |
| Promise.reject('🍏 #1'), | |
| Promise.reject('🍏 #2'), | |
| Promise.reject('🍏 #3'), | |
| ] | |
| Promise.allSettled(allRejectedPromises) | |
| .then(badApples => | |
| console.log(`We can't sell any of these apples...`, badApples)) | |
| .catch(error => console.error('This should never occur')) | |
| const promisesWithoutReject = [ | |
| Promise.resolve('🍎 #1'), | |
| '🍎 #2', | |
| new Promise((resolve, reject) => setTimeout(resolve, 100, '🍎 #3')) | |
| ] | |
| Promise.allSettled(promisesWithoutReject) | |
| .then(apples => console.log(`We can sell all these good apples`, apples.map(_=>_.value))) | |
| const promisesWithOneReject = [ | |
| Promise.resolve('🍎 #1'), | |
| new Promise((_, reject) => setTimeout(reject, 10, '🍏 #2')), | |
| '🍎 #3', | |
| new Promise((_, reject) => setTimeout(reject, 100, '🍏 #4')) | |
| ] | |
| Promise.allSettled(promisesWithOneReject) | |
| .then(apples => { | |
| const badApples = apples.filter(apple => apple.status === 'rejected').map(_ => _.reason) | |
| const goodApples = apples.filter(apple => apple.status === 'fulfilled').map(_ => _.value) | |
| console.log(`Let's throw out`, badApples, `and sell the rest`, goodApples) | |
| }) |
| We can't sell any of these apples... | |
| [ { status: 'rejected', reason: '🍏 #1' }, | |
| { status: 'rejected', reason: '🍏 #2' }, | |
| { status: 'rejected', reason: '🍏 #3' } ] | |
| We can sell all these good apples [ '🍎 #1', '🍎 #2', '🍎 #3' ] | |
| Let's throw out [ '🍏 #2', '🍏 #4' ] and sell the rest [ '🍎 #1', '🍎 #3' ] |
Regardless of settled (fulfilled or rejected) state, all promises resolve without short-circuiting to catch.
To differentiate if resolved values were successful, they are returned as an array of objects of following shape.
- Fulfilled promise is returned as {status: 'fulfilled', value}
- Rejected promise is returned as {status: 'rejected', reason}
🚀 Promise.race
- What is this? The first fulfilled promise or reject the whole promise when even one promise rejects.
- Idiom - A race between Good 😇 (Fulfilled) and Evil 😈 (Rejected)
- Not really an idiom though 😅
- Characteristic - Short-circuits when an input value is settled
🔆 Example
| const promiseWillFulfill = [ | |
| new Promise((resolve, reject) => setTimeout(reject, 250, '😈')), | |
| new Promise((resolve, reject) => setTimeout(resolve, 150, '😇')), | |
| new Promise((resolve, reject) => setTimeout(resolve, 1, '😇')), | |
| ] | |
| Promise.race(promiseWillFulfill) | |
| .then(value => console.log(`The humanity survives "${value}"`)) | |
| .catch(error => console.log(`Won't be called as 😇 will win the race`)) | |
| const promiseWillReject = [ | |
| new Promise((resolve, reject) => setTimeout(resolve, 250, '😇')), | |
| new Promise((resolve, reject) => setTimeout(reject, 1, '😈')), | |
| new Promise((resolve, reject) => setTimeout(resolve, 250, '😇')), | |
| ] | |
| Promise.race(promiseWillReject) | |
| .then(value => console.log(`This won't be called...="${value}"`)) | |
| .catch(error => console.log(`The humanity is doomed...="${error}"`)) | |
| const promisesWithOUTReject = [ | |
| new Promise(resolve => setTimeout(resolve, 350, 'one')), | |
| new Promise(resolve => setTimeout(resolve, 250, 'two')), | |
| new Promise(resolve => setTimeout(resolve, 150, 'three')), | |
| ] | |
| Promise.race(promisesWithOUTReject) | |
| .then(value => console.log(`Promise without reject="${value}"`)) |
| The humanity survives "😇" | |
| The humanity is doomed...="😈" | |
| Promise without reject="three" |
In promiseWillFulfill example, the first promise fulfilled within 1 millisecond and thus the humanity survived.
But the second example using promiseWillReject had a promise rejecting in 1 millisecond and thus the humanity is doomed.
And the last example (promisesWithOUTReject) fulfilled without rejection thus the first fulfilled promise value of "
three" was returned.
From these examples, you can see that the first settled state (fulfilled or reject) short circuited the promise.
🚀 Promise.any
- What is this? Returns the first fulfilled promise regardless of other rejected promises. If all promises reject, then reject by providing errors for all rejects.
- Idiom - All's well that ends well.
- Characteristic - Short-circuits when an input value is fulfilled.
- Note - Not yet implemented in any browsers and it is in Stage 1.
🔆 Example
| // Example #1 | |
| Promise.any([ | |
| Promise.reject('✗'), | |
| Promise.reject('✗'), | |
| Promise.resolve('✓'), | |
| Promise.reject('✗'), | |
| ]).then(function(value) { | |
| console.log(`You win at life`, value) | |
| }); | |
| // Example #2 | |
| // You get the first fulfilled value | |
| Promise.any([ | |
| new Promise((_, reject) => setTimeout(reject, 10, '✗')), | |
| new Promise((_, reject) => setTimeout(reject, 20, '✗')), | |
| new Promise((_, reject) => setTimeout(reject, 30, '✗')), | |
| new Promise(resolve => setTimeout(resolve, 100, 'I got a job!')), | |
| new Promise(resolve => setTimeout(resolve, 1000, 'I could have gotten a better job!')), | |
| ]).then(function(value) { | |
| console.log(value) | |
| }); | |
| // Example #3 | |
| // You get all rejection reasons | |
| Promise.any([ | |
| Promise.reject('✗'), | |
| Promise.reject('✗'), | |
| ]).catch(function(reasons) { | |
| console.log(`Didn't get any offers...`, reasons) | |
| }); |
| // Note that the result of Example #3 is printed first because of `setTimeout` in example #2 | |
| // Result of Example #1 | |
| You win at life ✓ | |
| // Result of Example #3 | |
| Didn't get any offers... [ '✗', '✗' ] | |
| // Result of Example #2 | |
| I got a job! |
First example has promises that rejects right away but did not short-circuit because of a fulfilled promise, thus you win at life.
Second example has promises resolving after a certain period. The first fulfilled promise was resolved after a series of rejects but didn't short-circuit. And you were able to get a job.
When all promises reject, then that's when Promise.any rejects and you didn't get any job offers.
👋 Conclusion
How I understood was that the new Promise.allSettled/any are introduced for Promise to try its best to resolve promises to fulfill unlike existing ones that fails on first encounter of reject.
Promise.all & Promise.race has been available in modern browsers (this exclude IE ;p) and Promise.allSettled will be available in Chrome 76.
Promise.any is still in stage 1 and not available in any browsers (but available in Bluebird or using polyfills - for the demo I used promise-any NPM library for demo.)
I'd love to hear where you would (have) use(d) each method to solve a problem.
And would you please kindly let me know if you find any mistakes and/or how I can improve the example?