DEV Community

loading...
Cover image for How to create Promises and handle Promise chains in JavaScript

How to create Promises and handle Promise chains in JavaScript

coderslang profile image Coderslang: Become a Software Engineer Originally published at learn.coderslang.com on ・4 min read

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);
Enter fullscreen mode Exit fullscreen mode

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 ...");
  }
});
Enter fullscreen mode Exit fullscreen mode

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..."
  })
Enter fullscreen mode Exit fullscreen mode

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(...)
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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));  
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Learn Full Stack JavaScript

Discussion (2)

Collapse
mdchaney profile image
Michael Chaney

const hasWon = Math.random() > 0.5 ? true : false;

Great article, but please understand that the above code causes a lot of us physical pain.

Collapse
coderslang profile image
Coderslang: Become a Software Engineer Author

Ooops 🤭. Thank you for the comment, Michael. Fixed.

Forem Open with the Forem app