2019-05-18

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

javascript

banner

title: Promise.race vs. Promise.any And Promise.all vs. Promise.allSettled date: "2019-05-18" banner: ./images/featured-image-1.jpg published_at: "2019-05-18T21:32:46.000Z" tags: javascript author: Sung M. Kim

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

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 🍏
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'))
]
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}"`))
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)
});
// 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!
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?