JavaScript: Promises

Subscribe to my newsletter and never miss my upcoming articles

Promises, a feature part of the ECMAScript 2015 (ES6) specification, plays a very important role in asynchronous programming in JavaScript. With Promises, we can write code that takes time to complete and then do something after the code completes executing. Okay, so why not use Callback Functions?

Well, callbacks can be used, but they have their demerits. When we want to perform some sort of chaining for different processes after a waiting code completes, chaining callbacks could lead to problems like Callback Hell. Basically, code becomes hard to understand or maintain when callbacks are used.

Promises provide a sweet way of handling chaining several actions when code completes. What are Promises in JavaScript, anyway?

A real-world scenario

Mrs. Yawson promised to buy her son, Jojo, an iMac on one Thursday. When it was finally Thursday, Mrs. Yawson left for the mall telling her son she was going to get the iMac.

While Jojo rejoiced and waited for his mother to return, the promise his mother made was pending. Assuming she returned with the iMac, the promised had been fulfilled, but if she returns saying iMacs weren't available then, the promise would have failed.

If the promise is fulfilled Jojo would set up the iMac in his bedroom and start gaming. If it failed he would go outside the house and cry.

So, what's a JavaScript Promise?

A JavaScript Promise is an object which represents an asynchronous operation (a pending block of code) which will eventually complete (become successful, or be fulfilled) or fail, providing a resulting value.

A JavaScript Promise has three states; it can (or can be)

  1. pending - while the code waits to complete.
  2. resolve - when the Promise completes successfully.
  3. reject - when the Promise fails.

Okay, let's digest this.

A Promise in JavaScript is simply a piece of code which needs some time before it completes, either becoming successful of a failure. Let's break this down using the scenario above.

  • A JavaScript Promise can be assumed as the promise Mrs. Yawson made to her son, Jojo. If her promise fulfills, Jojo should set up his machine and start gaming. Else, he should move out and cry.
  • A Promise is assumed to have been created when Mrs. Yawson went to the mall telling her son she was going for the iMac.
  • The status of the Promise is said to be pending while Jojo waits for his mother to come back.
  • The Promise would resolve if Mrs. Yawson brings the iMac, fulfilling her promise.
  • The Promise would reject if she could not bring the iMac, failing her promise.
  • Jojo's setting up the iMac and gaming when his mother fulfils her promise is the action to be taken when the promise resolves.
  • Jojo's moving out to cry when his mother fails her promise is the action to be taken when the promise rejects.

Let's go technical

As said earlier, a JavaScript Promise represents a code that takes some time to complete. For example, making a request to a server or making an API call. With a Promise, we can write some asynchronous operation (e.g. fetching a file data.json from a server) and do something with the result when the request is successful (i.e. a resolved Promise), or do something else when the request fails (i.e. a rejected Promise).

Creating a JavaScript Promise

A Promise in JavaScript is an object. Hence, it is instantiated with the Promise constructor, not forgetting the new keyword. The constructor accepts a function ( called the executor) as a parameter, which also takes two parameters. The code is show below:

let promise = new Promise ( function (resolve, reject) {
    // executor body ...
});

With the use of fat arrows syntax the above code can rewritten as:

let promise = new Promise ( (resolve, reject) => {
    // executor body ...
});

Now let's talk about the code above.

  • The executor (anonymous function) passed as parameter to new Promise runs automatically when the instantiation happens. It is in this function that we write the asynchronous operation, or in other words, the code that needs some time to complete. From the real-world scenario, let's assume the executor is Mrs. Yawson (and her promise to get Jojo an iMac).
  • The parameters that the executer accepts, resolve and reject, are predefined callback functions in JavaScript. When the executor gets any result, we pass it to either of the callbacks based on the type of result the executor receives. If the asynchronous operation is successful, the resolve callback is used to handle it like so: resolve( value ); if the operation fails, the reject callback is used to handle it like so: reject( error ). So, depending on the result received by the executor, it should, at the end, call either resolve or reject.

Some properties returned by a Promise

  • state - Initially when the Promise is waiting to complete, this property's value is 'pending', which changes to 'fulfilled' or 'rejected' when the Promise has fulfilled or failed, respectively.

  • result - When the Promise is pending, its value is undefined, when the Promise resolves, it becomes value, and error if it rejects.

Let's see an example.

Example

In the following example, we have an array of numbers. Using a Promise, we'll check if the array has at least 5 elements. The Promise is meant to be successful when the array has 5 elements. If it doesn't, the Promise fails. We'll use the global setTimeout function to delay the execution for 2 seconds (equivalent to 2000 milliseconds).

// here's our array of numbers
let nums = [1, 2, 3, 4, 5, 6];

// our Promise instance
let isAtLeast5 = new Promise( function (res, rej) {

    // delaying execution
    setTimeout(function() {

        // checking if the array has at least 5 elements
        if (nums.length >= 5) {

          // handle the success with res
          res('Array has at least 5 elements.');
        } else {

            // handle the failure with rej
            rej(new Error('Array has less than 5 elements.'));
        }
    }, 2000);
});

Explanation:

  • In the example above, I wrote res and rej instead of resolve and reject, respectively. You can also choose to name them any way you prefer. Just remember that resolve must come before reject. However, it's a good practice to use identifiers based on their purpose, so that you don't get confused somehow when your code grows.
  • In this example, the string 'Array has at least 5 elements.' is the result (value) to be resolved by the res callback when the Promise is fulfilled.
  • Similarly, the Error typed object 'Array has less than 5 elements.' is the result (error) to be caught by the rej when the Promise fails. It is okay to use a primitive value as we did with the res callback. It is just recommended to use Error objects when rejecting Promises.

At the moment when we run this piece of code, we see no display or anything of the sort. That's because we haven't written what happens when the Promise completes.

Performing action when Promise completes

The properties state and result of a Promise are internal and hence cannot be accessed directly. This means we cannot listen for a change event on the state and result properties and do something when the Promise completes. No need; JavaScript provides the then(), catch() and finally() methods for handling this situation.

Let's use the iMac scenario again. Yayy! :D

The .then() Method

The .then() method takes two parameters, both callbacks. The first callback takes the resolved result as a parameter and is fired when the Promise is fulfilled. The second callback takes the rejected result as a parameter and is fired when the Promise fails.

Based on the iMac scenario, take the .then() method as what makes Jojo set up the iMac and start gaming or move out and cry when his mother fulfills or fails the promise, respectively.

Example

We're going to use the isAtLeast5 instance created in the code above to show the syntax of the .then() method.

isAtLeast5.then(

    // function for handling Promise-success situation
    function (resolvedResult) { 

        /* do something with the resolved value */
        ...

    }, // notice the comma

    // function for handling Promise-failure situation
    function (rejectedError) { 

        /* do something with the rejected error */
        ...
    }
);

Explanation

  • The first anonymous function is invoked when the Promise resolves (completes successfully). The argument resolvedResult passed to it stores the result that was passed as parameter to the res callback of the executer when we instantiated the isAtLeast5 Promise. In the body of this function, we can write some codes which will run when the Promise resolves. We can also do something with the resolvedResult variable.
  • Similarly, the second anonymous function is invoked when the Promise is rejected (completes with an error). The argument rejectedError passed to it stores the result that was passed as parameter to the rej callback of the executer when we instantiated the isAtLeast5 Promise. In the body of this function, we can write some codes which will run when the Promise fails. We can also do something with the rejectedError variable. However, this second function is optional, as it is replaceable with the .catch() method (we'll cover it in a second).

Tip:

Henceforth, I'll embed pens from Codepen and since I'll be displaying text on the pen's iFrame DOM, I'll be using a log() function which writes the iFrame DOM. Here:

function log(str) {
    // create a new element
    let p = document.createElement('p');

    // set the innerHTML of the created element as the function parameter
    p.innerHTML = str;

    // append the element to the document body
    document.body.appendChild(p);
}

I'll be using the custom log() function but feel free to use console.log() if you want to.

Let's do something simple with the .then() method:

isAtLeast5.then(
    function (resolvedResult) {

        // print the resolvedResult 
        log(resolvedResult);
    },
    function (rejectedError) { 

        // print the rejectedError
        log(rejectedError)
    }
);

I believe the code above is well understood.

The .catch() Method

The .catch() method is the best alternative to the second anonymous function passed to the .then() method; its purpose is to handle a Promise-failed situation. The .catch() method takes one function as a parameter, which fires when the Promise fails. Simply put, it catches errors :D.

An example is implemented below: In order to see the .catch() method in action, we have to make the Promise fail. To do that, we have to reduce the number elements the nums array has to less than 5:

// rewriting the array, like
let nums = [1, 2, 3];

Now, let's use the .catch() method:

isAtLeast5.catch(function(error){
    // print the error
    log(error);
});

Explanation

  • The error argument of the callback stores the new Error() object we passed to the rej callback of the executor when we intantiated the isAtLeast5 Promise.

The .finally() Method

The .finally() method is ran whether or not a Promise resolves; so, whether the Promise was fulfilled or it failed, the callback passed to the .finally() method will run.

From the iMac scenario, whether or not Mrs. Yawson cam back with the iMac, she still arrived home.

In our number-of-elements-in-array example, we're going to display the number of elements in the array whether it has at least 5 elements (resolves) or has less than 5 (fails).

Example

promise.finally(function(){
       // whether or not the Promise fulfills, print the number of elements in array 
       log('Number of elements: ' + nums.length);
});

Chaining the .then(), .catch() and .finally() Methods

One advantage of using Promises over callbacks is it keeps our code clean and maintainable, and makes it more readable. With Promises we can write code in a more natural manner. Each of the methods return the Promise object after calling them. That's why we can chain them.

Example

Below, I'll demonstrate chaining of the .then(), .catch() and .finally() methods like...

// our array of numbers
let nums = [1, 2, 3, 4, 5, 6];

// our Promise instance
let isAtLeast5 = new Promise( function (res, rej) {
    setTimeout(function() {
        if (nums.length >= 5) {
          res('Array has at least 5 elements.');
        } else {
            rej(new Error('Array has less than 5 elements.'));
        }
    }, 2000);
});

// we can now use the methods after defining the Promise
isAtLeast5
    .then(function(value){
        log(value); // prints "Array has at least 5 elements."

    }) // when chaining avoid a semicolon
    .catch(function(err){
        log(err); // prints "Array has less than 5 elements."
    })
    .finally(function(){
        log('Number of elements: ' + nums.length);

    }); // use semicolon if you're done chaining

With the ES6 fat arrows syntax, we can rewrite the chaining as follows:

isAtLeast5
    .then( value => log(value) )
    .catch( err => log(err) )
    .finally( () => log('Number of elements: ' + nums.length) );

How sweet! :D

Explanation

  • In the above example, we see that only the .then() and .finally() methods are invoked. That's because the Promise resolved (completed successfully). An since it did, the .then() method was invoked, and the .finally() method doesn't care whether or not the Promise resolved; it fired anyway.
  • So, what about the .catch() method? It didn't run because the Promise did not fail. The array, indeed, has at least 5 elements. So, if we want to see the .catch() in action, we'll to rewrite the nums array to have less than 5 elements, like so:
    let nums = [1, 2, 3];
    

Now, we'll notice that the .catch() fired; because this time, the Promise failed.

Tip:

The order in which I've done my chaining can be changed the way you want it. You can bring the .catch() before the .then() method; it's just a matter of preference.

The Static Promise.all() Method

The .all() method is static. Which means it can only be accessed directly on the Promise, not an instance of it. Hence, we cannot use it like this:

let prom = new Promise((res, rej) => {});

// using it this way will yield an error
prom.all( ... );

What does it do, anyway?

It simply takes an iterable object of Promises, usually an array of Promises, as a parameter and waits for all the Promises to complete before doing something else. Say we're developing a weather-predicting app which makes two API calls using Promises:

  • promise1 - fetch the GPS location of the user,
  • promise2 - fetch weather information of the location.

We'd want to wait for both Promises to be successful before telling the user about the weather conditions of their current geographical location. In this situation, Promise.all() is the best solution!

[
    "Pokuasi", 
    "25 Degrees - Partly cloudy with thunderstorms"
]

... in the order in which we placed them in the array passed to Promise.all(). Since, Promise.all() returns a Promise, we can use the .then(), .catch() and .finally() methods on it:

Example:

// fake API call for GPS location, reolves after 1 second
// I ignored the second paramter `reject` because I don't need it
let getLocation = new Promise( resolve => {
    setTimeout( function() {
        resolve('Pokuasi')
    }, 1000 );
});

// fake API for weather condition, resolves after 4 seconds 
let getWeatherInfo = new Promise( resolve => {
    setTimeout( function() {
        resolve('25 Degrees - Partly cloudy with thunderstorms')
    }, 4000);
});


Promise.all( [getLocation, getWeatherInfo] )
    .then( function (forecastInfo) { 
              log(forecastInfo);
     }); 
    // yields Pokuasi, 25 Degrees - Partly cloudy with thunderstorms

Explanation

  • In the above example, we realize that the .then() method fires after 4 seconds. That's because the Promise.all() method waits for the second Promise getWeatherInfo (which fulfills after 4 seconds, as specified in the setTimeout).

If a Promise fails (rejects), Promise.all() immediately rejects, completely forgetting about the other ones in the list. Their results are ignored, even though they'll settle. So, when a .catch() method is added, its callback will rather be invoked.

The Static Promise.race() Method

The Promise.race() method is somehow like Promise.all(). Except, it only waits for the first Promise to settle, and then it gets its resolved result or rejected error. It is basically a RACE; hence the name.

The Promise.race() method, just like the Promise.all() method, takes an iterable object of Promises (usually an array) as a parameter, waits for the Promise which will settle first, and then Promise.race() takes its result.

By using the getLocation and getWeatherInfo Promises, we'll notice that we only get the result of the getLocation Promise since we used the seTimeout to make it resolve in 1 second, 3 seconds earlier before the getWeatherInfo Promise settles.

Let's see it in action...

Promise.race( [getLocation, getWeatherInfo] )
    .then( forecastInfo => log(forecastInfo); // yields "Pokuasi"

This ends our discussion on JavaScript Promises. Feel free to ask any question bothering your mind. I'll be glad to answer. :)

To conclude this series Asynchronous JavaScript, I'll be publishing a comprehensive article on async/await very soon. Adios!

Anthony Kiplimo's photo

This is awesome! Thanks for this man. What do you think of the async - await syntax?

Anthony Kiplimo's photo

VP Ecosystems @ Decoded Africa | DevRel @ Africa’s Talking

For anyone asking a similar question as mine. This might be of help: javascript.info/async-await

Gyen Abubakar's photo

An aspiring Fullstack Developer, currently mastering front-end. I love writing and teaching others the little I know. Join me on my journey.

async/await is also very awesome, makes asynchronous programming very convenient also. My next article will be on it.

Onlinebride's photo

Thanks for your information. With the help onlinebride.net loneliness will leave you forever. One has only to contact us and you can find your soul mate.