In this tutorial you’ll learn how to create JavaScript Promises, how to handle promise chains and utilize the functions Promise.all and Promise.race.
If you're new to the topic you can start with learning about How asynchronous programming and Promises work in JS
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 has two parameters (also functions):
-
resolve
- used when everything went well and need to return the result -
reject
- 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;
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
.
JavaScript Promise Chains handling
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, their id
, and a list of their 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 resolved.
Top comments (2)
const hasWon = Math.random() > 0.5 ? true : false;
Great article, but please understand that the above code causes a lot of us physical pain.
Ooops 🤭. Thank you for the comment, Michael. Fixed.