The Complete Guide To JavaScript Promises (Explained)

When working with Async javascript, there are three main methods that you will interact with, namely Callbacks, Async/Await, and Promises. This post will focus on the latter – Promises in javascript. Promises are a powerful feature in asynchronous Javascript, and there is a high chance that you will be asked a question regarding Promises in most web developer interviews.

A Promise is an object in asynchronous Javascript representing a “value” that is not yet available at the moment. Once the Promise completes successfully, it will return a value that can either be “Promise-fulfilled” or “Promise-rejected.”

This post will give you an in-depth guide on Javascript Promises. It will use simple illustrations that are easy to understand and also show you how to use Promises in your Javascript code.

Promises in Layman’s Terms – Breakfast Example

You and your friend (Jim) live in the same apartment. One morning, you tell Jim to go to the bakery and get some Doughnuts while you prepare coffee. As Jim leaves, you say to him that if he doesn’t get Dougnuts, he should send you a text so that you can prepare some Pancakes. If he gets the Dougnuts, he should still text you so you can set up the table.

Jim makes a PROMISE and says, if he gets doughnuts, he will send you the message “Got Them – set up the table.” If he doesn’t get doughnuts, he will send you the text, “Sorry buddy – make pancakes.”

Relation to Javascript Promises

The table below will give you a detailed illustration of how our “Breakfast scenario” relates to Promises in Javascript.

Breakfast ScenarioJavascript Promises
1. Your Friend – JimPromise: Jim is the ‘Promise’ you are waiting to complete. In this case, the Promise will be ‘complete’ when he gets to the bakery.
2. Can get doughnuts/ Might not get doughnutsPromise Value: (at this moment, it’s not known since Jim has not reached the bakery)
3. Jim got doughnutsThe Promise was fulfilled: It returns the value = “Got Them – set up the table.
4. Jim didn’t get doughnutsThe Promise was rejected: It returns the value = “Sorry buddy – make pancakes.
5. Setup the tableSuccess callback: If Jim got doughnuts, you would set the table.
6. Make PancakesFailure callback: If Jim didn’t get doughnuts, you would make pancakes.

Promise States

Having looked at the illustration above, you will notice that a Promise in Javascript can be in one of three states:

  • Pending state: Also referred to as the “initial state.” Here, the return value is unknown since the Promise is not completed.
  • Fulfilled state: This state represents a successful operation of the Promise and returns a resolve() value.
  • Rejected state: This state represents a failed operation of the Promise and returns a reject() value.

Why Use Promises – Not Callbacks?

Before introducing Promises in ES6, developers used Callbacks to implement asynchronous Javascript. If you are working on a small application, it would be okay to use callbacks. However, when working with large and complex applications, you might use numerous nested callbacks that eventually lead to callback hell.

Below is an example of a callback hell in NodeJS.

One(function (resultsFromOne) {
  Two(resultsFromOne, function (resultsFromTwo) {
    Three(resultsFromTwo, function (resulstsFromThree) {
      Four(resultsFromThree, function (resultsFromFour) {
        Five(resultsFromFour, function (resultsFromFive) {
          Six(resultsFromFive, function (resultsFromSix) {
            console.log(resultsFromSix);
          });
        });
      });
    });
  });
});

That is just a sample code with only simple functions. Imagine if you used these callbacks in a complex NodeJS application where you have to “read and write” the filesystem. It would be hectic!

Promises to the Rescue

You can create Promises in Javascript with the help of the “new” keyword. Promises make use of the syntax below.

const myPromise = new Promise()

Now, the Promise constructor function takes one argument – a function. To keep things neat and clean, we recommend using an arrow function as shown below.

const myPromise = new Promise((resolve, reject)=>{
    
})

As you can see in the code snippet above, this arrow function takes two arguments – resolve and reject.

Tip: You cannot directly mutate the status of a Promise. You instead use the “Resolve” and the “Reject” functions.

  • Resolve: When the Resolve function is invoked, it changes the status of the Promise from pending to fulfilled.
  • Reject: When the Reject function is invoked, it changes the status of the Promise from pending to rejected.

Creating a Simple Promise

With the information you have gathered up to this point, you can start writing your first Promise in Javascript. See the code snippet below.

const myPromise = new Promise ((resolve,reject)=>{
    //If Jim got the dougnuts, the Promise is fulfilled
    //We call the Resolve function
    resolve("Set up the table")
    
    //If Jim didn't get dougnuts, the Promise is rejected
    //We call the reject function
    reject("Make Pancakes")
})

From the code snippet above, it’s clear that the return value of a Promise is determined by some condition. For example, in our breakfast scenario, the condition is “whether Jim gets the doughnuts or not”. The code below now includes a condition.

const myPromise = new Promise((resolve, reject) => {
  if ("Jim Got Dougnuts") {
    resolve("Set up the table");
  } 
  
  else if ("Jim Didn't get dougnuts") {
    reject("Make Pancakes");
  }
});

Now, you might wonder, “are Promises simply an if-else statement.” Well, not really. There is a feature that we have not yet talked about.

Javascript Promises give you two methods you can use to execute callback functions depending on the status change of the Promise.

  • .then(): This method is invoked if the Promise was fulfilled. In other words, the Promise invoked the resolve() function.
  • .catch(): This method is invoked if the Promise was rejected. In other words, the Promise invoked the reject() function.

The code below shows you how to use the .then() and .catch() methods with Promises.

myPromise.then((message)=>{
    console.log(message)// Here, we log the output of the Resolve() function
}).catch((error)=>{
    console.log(error) // Here, we log the output of the Reject() function
})

Now that you have looked at all the parts of a Promise in Javascript, you can convert the “breakfast scenario” to code as shown below.

const myPromise = new Promise ((resolve, reject)=>{
    let JimGotDougnuts = true; //We assume that Jim got the dougnuts
    if(JimGotDougnuts){
        resolve("Set up the table")
    }else{
        reject("Make Pancakes")
    }
})

myPromise.then((message)=>{
    console.log(message)
}).catch((error)=>{
    console.log(error)
})

//Output
//Set up the table

In the above code, since the condition is “True,” the Promise invoked the resolve() function. In turn, the .then() method was invoked and logged the message we got from the Promise.

Promise Chaining (Nested Promises)

Up to this point, using Promises in Javascript might not look that fascinating since we are using a simple example. However, Promises come in handy, especially in solving the issue of callback hells that we looked at above.

Take a look at the code below that makes use of callback functions.

function FuncOne(callback) {
  //Pass a condition
  callback()
}

function funcTwo(callback) {
  //Pass a condition
  callback()
}

function funcThree(callback) {
  //Pass a condition
  callback()
}

funcOne(() => {
  funcTwo(() => {
    funcThree(() => {
      console.log("All the functions executed")
    })
  })
})

In the code snippet above, there are three functions that execute in order. funcOne() will execute first, followed by funcTwo(), and lastly, funcThree() that would log an output if all the conditions were passed. That is quite some mess of using nested callback functions.

So, how would Promises solve such a problem? First, you will need to tweak the way you write your functions. See the code below.

function funcONe(){
    return new Promise((resolve, reject)=>{
        //Pass a condition
        resolve()
    })
}
function funcTwo(){
    return new Promise((resolve, reject)=>{
        //Pass a condition
        resolve()
    })
}
function funcThree(){
    return new Promise((resolve, reject)=>{
        //Pass a condition
        resolve()
    })
}

Here, we changed the function to make use of Promises. Now, you can use the syntax below to call the functions.

funcOne().then(() => {
    return funcTwo()
  }).then(() => {
    return funcThree()
  }).then(() => {
    console.log("All the functions executed");
  });

Here, we are chaining the .then() methods of one Promise to the next. A Promise returned from a .then() or .catch() can be used in the next .then() or .catch() method in the “Promise” chain.

You can go ahead and make the code much cleaner and easy to read, as shown below.

funcOne()
  .then(funcTwo)
  .then(funcThree)
  .then(() => {
    console.log("We did them all")
  })

Advanced Promise Methods

Up to this point, we have looked at the basic usage of Promises in Javascript. However, the Promise object comes with other methods you can use when writing async Javascript.

.finally()

The .finally() method is used similarly to the .then() and .catch() methods. The main difference is that the code in the .finally() method will be executed regardless the Promise fails or rejects.

const myPromise = new Promise ((resolve, reject)=>{
    let a = true
    if(a) resolve("Test Passed")
    reject("Test Failed")
})

myPromise.then((message)=>{
    console.log(message)
}).catch((error)=>{
    console.log(error)
}).finally(()=>{
    console.log("I will always run")
})

//Output
//Test Passed
//I will always run

Promise.all() Method

If you have several Promises that you want to execute in parallel, you can use the Promise.all() method. Promise.all() takes an array of all the Promises you wish to iterate and returns a single value – an array of the values returned in each Promise. However, this method also follows a set of conditions.

  • The Promise.all() will be resolved if all the Promises are resolved.
  • If the passed arguments in the Promise.all() array don’t return any Promise, it will resolve.
  • If any of the Promises fails/ rejects, the Promise.all() method will call the catch() method on the rejected Promise.

See the code below.

const promiseOne = new Promise((resolve, reject)=>{
    resolve("Returned Promise One")
})
const promiseTwo = new Promise((resolve, reject)=>{
    resolve("Returned Promise Two")
})
const promiseThree = new Promise((resolve, reject)=>{
    resolve("Returned Promise Three")
})

Promise.all([promiseOne,promiseTwo,promiseThree]).then((message)=>{
    console.log(message)
}).catch((error)=>{
    console.log(error)
})

//Output
//  [ 'Returned Promise One','Returned Promise Two','Returned Promise Three' ]

From the output above, you can see that all the Promises were resolved and returned an array of the values returned in each Promise.

Promise.allSettled() Method

The Promise.allSettled() method is quite similar to the Promise.all() method. The only difference is that Promise.allSettled() will wait for all Promises to either resolve or reject before invoking the .then() method.

Also the Promise.allSettled() only returns an array showing the status of all the Promises (whether a Promise was fulfilled or rejected).

const promiseOne = new Promise((resolve, reject)=>{
    resolve("Returned Promise One")
})
const promiseTwo = new Promise((resolve, reject)=>{
    resolve("Returned Promise Two")
})
const promiseThree = new Promise((resolve, reject)=>{
    resolve("Returned Promise Three")
})

Promise.allSettled([promiseOne,promiseTwo,promiseThree]).then((message)=>{
    console.log(message)
})

Output:

Promise.allSettled()

If any of Promise returned a reject(), you would see the “rejected” status in the log. See the code below.

const promiseTwo = new Promise((resolve, reject)=>{
    reject("Promise Two Failed")
})

Promise.allSettled([promiseOne,promiseTwo,promiseThree]).then((message)=>{
    console.log(message)
})

Output:

Promise.any() Method

This method is similar to the Promise.all() method as it also takes an array of Promises. However, unlike Promise.all() which will wait for all the Promises to resolve, the Promise.any() method only waits for one Promise to resolve before calling the .then() function on that Promise.

const promiseOne = new Promise((resolve, reject)=>{
    reject("Returned Promise One")
})
const promiseTwo = new Promise((resolve, reject)=>{
    resolve("Returned Promise Two")
})

Promise.any([promiseOne,promiseTwo,promiseThree]).then((message)=>{
    console.log(message)
})
//Output
//Returned Promise Two

In the code above, we have two Promises – (promiseOne and promiseTwo). promiseOne returns a reject() while promiseTwo returns a resolve(). Since the Promise.any() method only waits for one “Promise” to resolve, it will call the .then() method on promiseTwo.

Promise.race() Method

The Promise.race() is similar to the Promise.any() method. The only difference is that Promise.race() will wait for the first Promise to either resolve or reject before calling the .then() or .catch() method on that Promise.

Tip: Promise.any() only waits for the Promise that will be the first to resolve/ succeed. Promise.race() only wait for the Promise that will be the first to resolve or reject.

const promiseOne = new Promise((resolve, reject)=>{
    reject("Returned Promise One")
})
const promiseTwo = new Promise((resolve, reject)=>{
    resolve("Returned Promise Two")
})

Promise.race([promiseOne,promiseTwo]).then((message)=>{
    console.log(message)
}).catch((error)=>{console.log(error)})

//Output
//Returned Promise One

In the output above, we see that we returned promiseOne. That’s because it was the first Promise to return a value.

Conclusion

This post has given you a comprehensive guide on Javascript Promises. Below are key points you can always refer to when working with Promises.

  • Promise.all(): This method waits for all the Promises passed as an array to resolve and returns a single array of values returned in all Promises.
  • Promise.allSettled(): This method takes an array of Promises and waits for all the Promises to either resolve or reject. It will then return an array showing the status of each Promise (fulfilled or rejected) and the value returned in each Promise.
  • Promise.any(): This method takes an array of Promises and then waits for the first Promise to resolve before calling the .then() method on that Promise.
  • Promise.race(): This method takes an array of Promises and waits for the first Promise to either resolve or reject before calling the .then() or .catch() method on that Promise.

Was this article helpful?

Share it with other developers who want an in-depth explanation of Promises in Javascript. You can also share your thoughts by replying on Twitter of Become A Better Programmer or to my personal account.