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 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 ? 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!