Asynchronous programming in JavaScript in unimaginable without Promises and Promise chains. In this tutorial you’ll learn how to create JavaScript Promises, how to handle promise chains and utilise the functions Promise.all and Promise.race.
How to create a Promise in JavaScript
A Promise (and a couple of other things) can be created using the new
keyword:
const promise = new Promise(executor);
The executor
argument is a function that must have two parameters (also functions):
resolve
- used when everything went well and need to return the resultreject
- used if an error occurred
The executor
function is called automatically, however, we need to call resolve
or reject
inside of it ourselves.
Let’s write a coinflip
function that simulates a coin toss. It accepts a bet and in half of the cases it ends with an error, and in half of the cases it “thinks” for 2 seconds and returns the doubled bet.
const coinflip = (bet) => new Promise((resolve, reject) => {
const hasWon = Math.random() > 0.5 ? true : false;
if (hasWon) {
setTimeout(() => {
resolve(bet * 2);
}, 2000);
} else {
reject(new Error("You lost...")); // same as -> throw new Error ("You lost ...");
}
});
In the resolve function, we pass a value that will become available after the promise is fulfilled.
And in reject
- we throw an error. Technically we can use throw
instead of reject
. There will be no difference.
Let’s use our coinflip
.
coinflip(10)
.then(result => {
console.log(`CONGRATULATIONS! YOU'VE WON ${result}!`);
})
.catch(e => {
console.log(e.message); // displays the error message if the promise is rejected
// in our case: "You lost..."
})
As previously, if everything goes well, we will get the result inside then
. And we will handle errors inside catch
.
How to handle Promise chains in JavaScript
There are often situations where one asynchronous function should be executed after another asynchronous function.
For example, we can try to bet again if we managed to win a coinflip
. And then once again.
To do this, you can create promise chains. In general, they look like this:
promise
.then(...)
.then(...)
.then(...)
.catch(...)
The first .then
will return a promise, and another .then
can be attached to it, and so on.
Despite having multiple .then
blocks, a single .catch
will suffice, if placed at the very end of the chain.
Let’s add a little refactoring to avoid code duplication and try to win more coins.
const betAgain = (result) => {
console.log(`CONGRATULATIONS! YOU'VE WON ${result}!`);
console.log(`LET'S BET AGAIN!`);
return coinflip(result);
};
const handleRejection = (e) => {
console.log(e.message);
};
coinflip(10)
.then(betAgain)
.then(betAgain)
.then(betAgain)
.then(result => {
console.log(`OMG, WE DID THIS! TIME TO TAKE ${result} HOME!`);
})
.catch(handleRejection);
The betAgain function takes a number, displays the congrats message, and calls coinflip
again. Then we add as many .then
blocks as we need to complete the task.
In fact, we only needed betAgain
to display the debug messages. If we were just interested in the end result, then we could simply pass the coinflip
function to .then
. Like this:
coinflip(10)
.then(coinflip)
.then(coinflip)
.then(coinflip)
.then(result => {
console.log(`OMG, WE DID THIS! TIME TO TAKE ${result} HOME!`);
})
.catch(handleRejection);
Promise.all
- waiting for all Promises to resolve
Let’s return from our virtual casino to the real world.
Imagine we have a function getUserData
that returns the user’s name, his id
, and a list of his friends. Something like this:
{
id: 125,
name: 'Jack Jones',
friends: [1, 23, 87, 120]
}
We receive it, of course, not immediately, but after the promise becomes fulfilled
.
And we were given the task of displaying a list of all the user’s friends, but not just id
, but all their data.
We already know how to work with one promise, let’s start by displaying a list of id
friends on the screen:
getUserData(userId).then(console.log);
Next, we could try to take the list of friends and transform it with map
so that we have information about each friend:
getUserData(userId)
.then(userData => {
return userData.friends.map(getUserData);
})
.then(console.log)
.catch(e => console.log(e.message));
Not bad. But on the screen, we will see [Promise {<pending>}, Promise {<pending>}]
instead of full information about friends.
Unfortunately, we will not be able to add another then
or map
here, because we already have an array, and the promises inside of it are still in the pending
state.
To solve this problem, we need the Promise.all(array)
function. It takes an array of promises and returns a single promise.
This promise will become fulfilled
when all the promises from array
are resolved. And if at least one of them is rejected, then the whole Promise.all
will be rejected.
getUserData(userId)
.then(userData => {
return Promise.all(userData.friends.map(getUserData));
})
.then(console.log)
.catch(e => console.log(e.message));
Now the program works as expected and we display a list of all the user’s friends.
Promise.race
- waiting for the fastest promise
If we only need to get the result of the fastest Promise, then we can use the function Promise.race(arr)
.
Just like Promise.all
, it takes an array of Promises and returns a single Promise. But you cannot predict in advance the return value after it enters the fulfilled
state.
Promise.race
resolves with the value of the fastest Promise of the array.
const fastPromise = new Promise((resolve, reject) => {
setTimeout(() => resolve(`fast`), 100);
});
const slowPromise = new Promise((resolve, reject) => {
setTimeout(() => resolve(`slow`), 200);
});
const arr = [fastPromise, slowPromise];
Promise.race(arr).then(console.log); // fast
In this example, the message fast
will be displayed on the screen in 100 milliseconds and we will not wait for the second promise to be executed.
Read more JavaScript tutorials or Learn Full-Stack JS from scratch!