DEV Community

Cover image for Breaking down Promises in JavaScript
Valentina Peric
Valentina Peric

Posted on

Breaking down Promises in JavaScript

Promises became widely supported and popularized in JavaScript when ES6 was released in 2015. Although, fun fact, the term was proposed by Daniel P. Friedman back in 1975. By definition, a promise is an object that contains the results of the eventual completion or failure of an asynchronous operation. This article will break down what this all means.

Prerequisites 📝

  1. A working knowledge of javascript
  2. A code editor (I recommend Visual Studio Code)

Let's Get Started ✨

Three States of Promises

Promises have three different states depending on the operations that are being executed,

  1. Pending: the operation has not yet completed
  2. Fulfilled: the operation has completed successfully and the returned object is ready
  3. Rejected: The operation has failed

Cool, makes sense. Now, how does this look like in code?

const myPromise = new Promise((resolve, reject) => {
  //async operation

  if (success) {
    resolve("the operation has completed successfully!");
  } else {
    reject('"the operation has failed"); 
  }
});
Enter fullscreen mode Exit fullscreen mode

The promise constructor takes in one parameter which is the callback function. The callback function takes in two parameters which are the resolve and reject functions. While the asynchronous operation is executing, the promise is in a pending state. Once the operation of the promise has completed, the resolve function can be called to indicate the operation was successful, and the reject function if it has failed. The promise object returned from calling the Promise constructor can then be used for chaining.

Chaining Promises and Consumers

You can chain promises by using consumers such as .then(), .catch(), and .finally(). Let's go through each one,

.then() continues code execution and is invoked when a promise is either resolved or rejected. it takes in two arguments: the callback function of the fulfilled promise and the callback function for the rejected case.

.catch() is invoked when a promise is rejected. It is also invoked when some error has occurred during execution. Treated similar to the .then() but it does not take in an argument for when the promise is fulfilled.

.finally() gets invoked when the promise has settled, regardless of success or failure. This consumer takes no arguments.

Let's see how they all work together by making a cappuccino.

function boilWater() { 
  return new Promise((resolve, reject) => { 
    setTimeout(() => {
      resolve("water has boiled.");
    }, 2000);
  });
} 

function brewCoffee(water) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("coffee has been brewed.");
    }, 2000);
  });
}

function frothMilk() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("Milk has been frothed.");
      resolve();
    }, 2000);
  });
}

boilWater()
  .then((water) => {
    return brewCoffee(water);
  })
  .then(() => {
    return frothMilk();
  })
  .then(() => {
    console.log("Enjoy your cappuccino");
    return;
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    console.log("Continue coding!")
  });
Enter fullscreen mode Exit fullscreen mode

In the example above, boilWater is called first which returns a resolved promise that gets chained to the brewCoffee function. The chaining continues and once frothMilk returns a resolved promise, enjoy your cappuccino will be logged out. If any promise gets rejected or any other error occurs in the execution, the catch will log out the error. The finally consumer will get called regardless of success or failure. This is the routine that I have every morning :)

Next Steps ✨

You can see that promises can result in a lot of chaining and nested functions which can cause readability in your code to suffer. With async/await, a lot of this will be made simpler. You can read how here.

Top comments (0)