Promises are available since ES2015 to simplify the handling of asynchronous operations. Promises have two well-known combinators: all
and race
. Both of them are useful, but they don't cover all the use-cases. What if we want to wait for all the Promises even if some of them go to error or only the first successful operation is essential? I'll show you the new Promise combinators that will help you give the answers to these questions.
First, we will look at the combinators that we already have and then look at two upcoming ones.
Promise.all (docs)
The all
combinator takes multiple promises (an iterator, in most cases an array) and returns a single promise that resolves when all the promises are completed, or the iterator doesn't contain any element. There is no ordering of the given promises; they execute in parallel. However, the return value order of each input promise will be the same as were in the input. The returned promise will contain the value of the inputs in an array.
const first = Promise.resolve('Batman');
const second = Promise.resolve('Joker');
Promise
.all([first, second])
.then(results => {
// results = ['Batman', 'Joker']
});
What happens if one of the promises get rejected? The returned promise gets rejected, and we won't know what happened with the others.
const first = Promise.resolve('Batman');
const second = Promise.reject(new Error('Joker'));
Promise
.all([first, second])
.then(results => {
// we won't get here
})
.catch(error => {
// Error: Joker
});
We can't wait for all the promises to finish even if some of them would be fulfilled.
Promise.race (docs)
The promise returned from the race
function fulfills or rejects as soon as one of the passed promises resolve or reject. It is useful if you are interested in the first result and want to neglect the slow ones.
const first = Promise.resolve('Batman');
const second = Promise.resolve('Joker');
Promise
.race([first, second])
.then(result => {
// result = 'Batman' or 'Joker'
});
The same happens when one of the promises get rejected.
const first = Promise.resolve('Batman');
const second = Promise.reject(new Error('Joker'));
Promise
.race([first, second])
.then(result => {
// we get here
})
.catch(error => {
// or here
});
We can't wait for the first promise to resolve if the previous one is rejected.
The future
The upcoming versions give us two new combinators that will help us overcome the limitations of all
and race
. The new versions will also introduce new methods to handle failed operations easier.
Promise.allSettled (docs)
The all
combinator takes multiple promises and returns a single promise that resolves when all the inputs are completed or rejected. The big difference to the all
combinator is that it won't be rejected if one the promises reject. allSettled
will wait for all the others and return both fulfilled and rejected promise results.
const first = Promise.resolve('Batman');
const second = Promise.reject(new Error('Joker'));
Promise
.allSettled([first, second])
.then(results => {
// results = [
{ status: 'fulfilled', value: 'Batman' },
{ status: 'rejected', reason: Error: Joker }
]
});
For every item that is fulfilled, we get an object with the status
property fulfilled
and the value
property containing the return value of that promise. For rejected items, we get an object with the status
property rejected
and the reason
property containing the error.
If you want to separate rejected and fulfilled promises, you will have to run a quick filter on the result array.
Promise.any (docs)
The promise returned from the any
function waits until one of the supplied promises resolve. It will still resolve when some of the promises fail. If all the promises reject, the race
function will also reject.
const first = Promise.resolve('Batman');
const second = Promise.reject(new Error('Joker'));
Promise
.any([first, second])
.then(result => {
// result = 'Batman'
});
It is a great way to wait for the first successful operation to complete and ignore the rest.
Promise.prototype.finally (docs)
Promises have two states: fulfilled or rejected. When the promise is fulfilled it executes the then
callback, when rejected the catch
callback. What happens if we want to run some cleanup commands afterward? We have to include it in both callbacks. Here is where the finally
method becomes the gamechanger because it is called in both scenarios.
const second = Promise.reject(new Error('Joker'));
second
.then(result => {})
.catch(error => {})
.finally(() => {
// do some cleanup
});
Promise.try
The method try
receives a function that can throw synchronous errors and return rejected promises, and it will convert both types to a rejected promise.
Promise.try(() => {
throw new Error('Joker'); // synchronous
return Promise.reject(new Error('Joker')) // asynchronous
});
It can come in handy when you do synchronous operations before asynchronous ones, for example in a command-line script. If you have synchronous code, error handling will be in a try-catch block. Asynchronous codes error handling will be in a catch callback. With Promise.try
, you won't need separate error handling.
If the method is still not precise I would recommend reading this detailed article about the subject.
Availability
Promise.prototype.finally
and Promise.allSettled
are available in all modern browsers and in Node.js (from version 12), but the others are still in the draft stage. We need the corresponding shims to use them.
require('promise.allSettled').shim();
require('promise.any').shim();
require('promise.finally').shim();
require('promise.try').shim();
You can find them under ECMAScript shims.
Summary
Organizing asynchronous code got a massive leap with Promises. It became more comfortable and more readable. Nowadays, the TC39 committee is working on how to make it more natural. The result is the two new combinator functions (allSettled
, any
), and the finally
method along with the try
method. If you like these methods, start using them today with one of the shims.
Top comments (6)
I don't really understand what is the use of
try
since throwing in a promise was already handled:Would reject like any other promise. I do like the other ones though ;p
At entry scripts where you start synchronous, it can help.
What does Javascript Promise us for 2020?
Ha! I see what you did there. ♥
Pun intended :)
Thanks that was a great read! 🤩👍
Thanks!