Promises are one of the most celebrated features introduced to JavaScript. Having a native asynchronous artifact baked right into the language has opened up a new era, changing not only how we write code but also setting up the base for other freat APIs — like fetch
!
Let’s step back a moment to recap the features we gained when they were initially released and what new bells and whistles we’re getting next.
New to the concept of Promises? I highly recommend Jake Archibald’s article as a primer.
Features we have today
Let’s take a quick look at some of the things we can currently do with promises. When JavaScript introduced them, it gave us an API to execute asynchronous actions and react to their succesful return or failure, a way to create an association around some data or result which value we still don’t know.
Here are the Promises features we have today.
Handling promises
Every time an async method returns a promise — like when we use fetch — we can pipe then()
to execute actions when the promise is fulfilled, and catch()
to respond to a promise being rejected.
fetch('//resource.to/some/data')
.then(result => console.log('we got it', result.json()))
.catch(error => console.error('something went wrong', error))
The classic use case is calling data from an API and either loading the data when it returns or displaying an error message if the data couldn’t be located.
In addition, in its initial release we got two methods to handle groups of Promises.
Resolving and rejecting collections of promises
A promise can be fulfilled when it was successfully resolved, rejected when it was resolved with an error, and pending while there’s no resolution. A promise is considered settled when it has been resolved, disregarding the result.
As such, there are two methods we have to help with the behavior of handling a group of promises depending on the combination of states we obtain.
Promise.all
is one or those methods. It fulfills only if all promises were resolved successfully, returning an array with the result for each one. If one of the promises fails, Promise.all
will go to catch
returning the reason of the error.
Promise.all([
fetch('//resource.to/some/data'),
fetch('//resource.to/more/data')
])
.then(results => console.log('We got an array of results', results)
.catch(error => console.error('One of the promises failed', error)
In this case, Promise.all
will short-circuit and go to catch
as soon as one of the members of the collections throws an error, or settle when all promises are fulfilled.
Check out this short writing about promises states by Domenic Denicola for a more detailed explanation about the wording and concepts about them.
We also have Promise.race
, which immediately resolves to the first promise it gets back, whether it was fulfilled or rejected. After the first promise gets resolved, the remaining ones are ignored.
Promise.race([
fetch('//resource.to/some/data'),
fetch('//resource.to/other/data')
])
.then(result => console.log('The first promise was resolved', result))
.catch(reason => console.error('One of the promises failed because', reason))
The new kids on the block
OK, we’re going to turn our attention to new promise features we can look forward to.
Promise.allSettled
The next proposed introduction to the family is Promise.allSettled
which, as the name indicates, only moves on when the entire collection members in the array are no longer in a pending status, whether they were rejected or fulfilled.
Promise.allSettled([
fetch('//resource.to/some/data'),
fetch('//resource.to/more/data'),
fetch('//resource.to/even/more/data')
])
.then(results => {
const fulfilled = results.filter(r => r.status === 'fulfilled')
const rejected = results.filter(r => r.status === 'rejected')
})
Notice how this is different from Promise.all
in that we will never enter in the catch
statement. This is really good if we are waiting for sets of data that will go to different parts of a web application but want to provide more specific messages or execute different actions for each outcome.
Promise.any
The next new method is Promise.any
, which lets us react to any fulfilled promise in a collection, but only short-circuit when all of them failed.
Promise.any([
fetch('//resource.to/some/data'),
fetch('//resource.to/more/data'),
fetch('//resource.to/even/more/data')
])
.then(result => console.log('a batch of data has arrived', result))
.catch(() => console.error('all promises failed'))
This is sort of like Promise.race
except that Promise.race
short-circuits on the first resolution. So, if the first promise in the set resolves with an error, Promise.race
moves ahead. Promise.any
will continue waiting for the rest of the items in the array to resolve before moving forward.
Demo
Some of these are much easier to understand with a visual, so I put together a little playground that shows the differences between the new and existing methods.
Wrap-up
Though they are still in proposal stage, there are community scripts that emulate the new methods we covered in this post. Things like Bluebird’s any
and reflect
are good polyfills while we wait for browser support to improve.
They also show how the community is already using these kind of asynchronous patterns, but having them built-in will open the possibilities for new patterns in data fetching and asynchronous resolution for web applications.
Besides then and catch you can pipe finally to a promise, Sarah Drasner wrote a detailed piece about it that you can check out.
If you want to know more about the upcoming Promise
combinators, the V8 blog just published a short explanation with links to the official spec and proposals.
Promise.finally() is also a very useful feature.
It comes in handy when you have duplicate code in the .then() and .catch(), by moving that part in the finally() statement, you can keep it DRY.
Yes,
finally
is a useful handler, this article though is more centered in Promise combinators and not handlers, also Sarah already wrote about it here in CSS Tricks, I will add a mention to it in the article. Thanks for the comment!@jmenichelli: How do you get to know about the coming features in advance?
Please let me know if you know about such pages and blogs from which I can check such things too.
Depends on how you usually consume information about web technologies, I use twitter a lot so I folow the V8 team account and their engineers to find out what they are working on, iterating or implementing over Chrome. There are also RSS feed and newsletter curated by members of the community you can subscribe to and received them in your email inbox.
I can’t find in the spec what would happen when using Promise.allSettled().catch(). Do errors have to be handled in then() ? FolktaleJS provides a Task as a Promise abstraction, with a waitAny() interface that waits for all tasks to complete, before returning the first errors, or the array of the resolved task return values. Is it the same as Promise.allSettled()?
So, I thought this was tricky too. In my early tests it always goes to
then
as part of the array, you can later filter and inspect thereason
property. I don’t know if an error thrown, besides thePromise
calling the reject handler could sent you to catch, my guess is that it won’t.