Promise.race vs. Promise.any And Promise.all vs. Promise.allSettled

featured
May 18, 2019
💫 Originally posted here. Broken? Let me know ~

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

Short-circuit? Short-circuits on? Fulfilled on? Rejected on?
Promise.all First rejected promise All promise fulfilled First rejected promise
Promise.allSettled N/A Always N/A
Promise.race First settled First promise fulfilled First rejected promise
Promise.any First fulfilled First promise fulfilled All rejected promises
view raw Comparison Table.md hosted with ❤ by GitHub

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))
view raw 1. Promise.all.js hosted with ❤ by GitHub
We can sell all these good apples [ '🍎 #1', '🍎 #2', '🍎 #3' ]
Threw out all apples because of this Bad 🍏
view raw 2. Output hosted with ❤ by GitHub


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'))
]
const extractApples = apples => apples.map(_ => _.value)
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' ]
view raw 2. Output hosted with ❤ by GitHub


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}"`))
view raw 1. Promise.race.js hosted with ❤ by GitHub
The humanity survives "😇"
The humanity is doomed...="😈"
Promise without reject="three"
view raw 2. Output hosted with ❤ by GitHub


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)
});
view raw 1. Promise.any.js hosted with ❤ by GitHub
You win at life ✓
Didn't get any offers... [ '✗', '✗' ]
I got a job!
view raw 2. Output hosted with ❤ by GitHub


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?