DEV Community

Megan Lo
Megan Lo

Posted on • Edited on

Promises in JavaScript

Hope you had a great break before continuing the series!

In this article, we would cover Promises. If you haven't read the previous article (Intro to Asynchronous JS), I highly recommend you to first read it before coming back to this article, as it builds an important foundation for this article.

coffee dive

There are 4 parts in this series:

  1. Intro to Asynchronous JS
  2. Promises (this article)
  3. More Promises
  4. async/await

Introduction

Promises was introduced in ES6 to simplify asynchronous programming.

I would divide this article into the following sections:

  • Why was Promises introduced? (Spoiler Alert: Trouble with callbacks)
  • Promise Terminology
  • Basic Promise usage
  • Promise Consumer: then, catch, finally

In the next article, we'll cover:

  • Chaining Promise
  • Fulfilling multiple Promises

Before Promises: Old-style Callbacks

Before the introduction of Promises in ES6, asynchronous was commonly handled with callbacks (calling a function within another function). This is important to know before diving into Promises.

Let's see some callback example. Imagine you are ordering Starbucks coffee on a Monday morning and you are feeling cranky. Unfortunately, you don't just get your coffee with a snap.
Thanos snap

You have to first decide what kind of coffee you want, then you place your order with the barista, then you get your coffee, last but not least,
monkey sipping meme

Here's how the callback's going to look like (reference to MDN doc on Promise):

chooseCoffee(function(order) {
  placeOrder(order, function(coffee) {
    drinkCoffee(coffee);
  }, failureCallback);
}, failureCallback);
Enter fullscreen mode Exit fullscreen mode

As you can see, it's very messy-looking! This is what is often referred to as "callback-hell". Promises allow these kinds of nested callbacks to be re-expressed as Promise chain, which we would cover more in the next article.

In the following section, we would first cover the terminology, then we'll dive into the basic Promise usage using the callback functions we saw in the series.

Promise Terminology

Here's the basic syntax of Promise:

let promise = new Promise(function(resolve, reject) {
  // executor
});
Enter fullscreen mode Exit fullscreen mode

The arguments resolve and reject are the two callbacks provided by JavaScript.

Here are the three states in a promise you need to know:

  1. pending: when a promise is created, it is neither in success or failure state.
  2. resolved: when a promise returns, it is said to be resolved.
  3. fulfilled: when a promise is successfully resolved. It returns a value, which can be accessed by chaining a .then block onto the end of the promise chain (will discuss this later in the article)
  4. rejected: when a promise is unsuccessfully resolved. It returns a reason, an error message why it is rejected (Error: Error here). This can be accessed by chaining a .catch block onto the end of the promise chain.

Here's a more visual graph from javascript.info
Promise graph

Basic Promise usage

In a promise, there can be only one result or error. So let's say we have this Promise function:

let promise = new Promise(function(resolve, reject) {
  resolve("done");
  reject(new Error("Not okay!")); // ignored
})
Enter fullscreen mode Exit fullscreen mode

The result will immediately show "done" and the error will be ignored.

This is the easy version. This is what you can expect how a promise will look like:

  let example = () => {
   return new Promise(function(resolve, reject) => {
     let value = function1();
     if (job success) {
       resolve(value);
     } else (job unsuccessful) {
       reject(console.log("something's wrong!! :("));
     }
   }).then(function(value)) {
     // success
     return nextFunction(value);
   }).catch(rejectFunction);
  }
Enter fullscreen mode Exit fullscreen mode

Okay, it's getting a lot and we've got some new friends in the above function. What's .then and .catch? We will get to them in the next section, but just a quick breakdown from above:

  • As a promise is created, if the function is passed successfully, the promise will be resolved.
  • On the other hand, if the function is passed unsuccessfully, the promise will be rejected and "something's wrong!! :(" will be printed on the console.

This is all you need to know for now! Let's move to our consumers in Promises!

Consumers: .then, .catch, .finally

A Promise object serves as a connection between the executor (you know the resolve, reject) and the consuming functions. Consuming functions can be registered with methods: then, catch, finally (which you've already seen .then and .catch in the previous section!).

Here's the promise cycle:
1️⃣ A promise is created (State: Pending)
2️⃣ a
πŸ‘‰πŸ» A promise is resolved (State: resolved)
πŸ‘‰πŸ» Promise chain with .then
2️⃣ b
πŸ‘‰πŸ» A promise is rejected (State: rejected)
πŸ‘‰πŸ» .catch to handle errors
3️⃣ .finally to give the final result of the promise πŸ’―

.then

As I learned how to use these consumer methods, I like to think .then as... then what would you like to do after we resolved the promise?

Consider .then is similar to AddEventListener(). It doesn't run until an event occurs (i.e. the promise is resolved).

let promise = new Promise(function(resolve, reject) {
  setTimeOut(() => resolve("done!!"), 1000);
});

promise.then(
  // shows "done!" in console after 1 second
  result => console.log(result) 
);
Enter fullscreen mode Exit fullscreen mode

Note: You can show errors using .then.

.catch: Error Handling

Promises is not always resolved, but there are cases where promises is rejected. Therefore .catch is here to catch errors.

Here's how we remember:

  • .then works when a promise is resolved.
  • .catch works when a promise is rejected.

If we are interested in seeing errors, here's how we use .catch:

let promise = new Promise(function(resolve, reject) {
  setTimeOut(() => reject(new Error("NO!")), 1000);
});

// shows the error after 1 second
promise.catch(result => console.log(result));
Enter fullscreen mode Exit fullscreen mode

Feel free to copy the code above to your terminal/Chrome DevTool (if you are using Chrome). You should see the following:
.catch demo

Note: .then(null, func) is the same as .catch(func)

.finally

.finally, which is introduced in ES2018, is like a decent closure for the promise. Think of it like finally we are done and it's time to disclose the final result. In other words, it works no matter the promise is resolved or rejected.

.finally is a good handler for performing cleanup, like stopping the loading indicator.

If a promise is resolved:

let promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve("done!"), 2000);
})

promise.finally(() => console.log("Promise ready"));
promise.then(result => console.log(result)); 
Enter fullscreen mode Exit fullscreen mode

Quick Demo:
resolved promise finally demo

If a promise is rejected:

let promise = new Promise((resolve, reject) => {
  throw new Error("error");
})
promise.finally(() => console.log("Promise ready"))
promise.catch(err => console.log(err)); 
Enter fullscreen mode Exit fullscreen mode

Quick Demo:
rejected promise finally demo

Well let's put all these together, shall we?

Let's apply our new knowledge to our Monday morning coffee from the callback-hell section, but I will add one more condition is that we will only buy coffee if our mood rates lower than or equal to 5 (out of 10):

function orderCoffee() {
  return new Promise((resolve, reject) => {
    let rating = Math.random() * 10;
    // this is only a reference so that 
    // we know what the rate of mood is
    console.log(rating);
    if (rating > 5) {
      resolve("I AM FEELING GREAT!");
    } else {
      reject(new Error("We are going to Starbucks..."));
    }
  });
}

orderCoffee()
  .then(mood => console.log(mood))
  .catch(err => console.log(err))
  .finally(() => console.log("Decision's been made!"));
Enter fullscreen mode Exit fullscreen mode

(Code reference from MDN's Promise.prototype.finally())

If the mood rates higher than 5 (i.e. the promise is resolved) (You can see the number on the first line after the handlers):
Resolved Promise Coffee Example

If the mood rates lower than or equal to 5 (i.e. the promise is rejected):
Rejected Promise Coffee Example

Feel free to copy the code above on your ChromeDevTool/terminal to play around!!

A quick recap on this section:

  • If a promise is resolved, .then will take over the rest.
  • If a promise is rejected, .catch will take over and return the error.
  • .finally is good for performing cleanup as it works no matter the promise is resolved and rejected.

Alright, these are the basics of Promises! In the next article, we'll talk more about chaining promises and fetching multiple promises!

Here's a quick glimpse using Promise chain from our callback example:

chooseCoffee()
.then(order => placeOrder(order))
.then(coffee => drinkCoffee(coffee))
.catch(failureCallback);
Enter fullscreen mode Exit fullscreen mode

Resources:

🌟 Highly Recommend: Promise (javascript.info)
🌟 Eloquent JavaScript Chapter 11: Asynchronous Programming
🌟 JavaScript The Definitive Guide by David Flanagan (7th Edition) Chapter 13.2: Promises (Pg. 346 - 367) (Amazon)
🌟 Graceful asynchronous programming with Promises (MDN)
🌟 JavaScript Async/Await Tutorial – Learn Callbacks, Promises, and Async/Await in JS by Making Ice Cream 🍧🍨🍦 (FreeCodeCamp)
🌟 How To Implement Promises in JavaScript?

πŸ¦„ If you are looking for more explanation (or a different way of explaining) on this concept, I'd like to recommend my friend, Arpita Pandya's article:
JavaScript Promises

Top comments (0)