Promises

The idea of the Promises is not something came to JavaScript with ES2015, it was there way before. The need of the Promises arose because of the asynchronous nature of the language. A Promise represents the eventual result of an asynchronous operation.

The Promise object represents the possible completion (or failure) of an asynchronous operation and its resulting value.

The syntax of the promise looks something like the following.

new Promise(function(resolve, reject) { ... } );

The function that accepts resolve and reject as its arguments is known as the executer, because it is executed immediately by the Promise implementation and is called before the Promise constructor even returns the created object. The resolve and reject are functions that resolve or reject the promise, respectively.

A Promise could be in one of the following states:

  • pending: initial state, neither fulfilled nor rejected.
  • fulfilled: meaning that the operation completed successfully.
  • rejected: meaning that the operation failed.

The following picture shows each state in more details.

Promise states on MDN

Apart from the constructor as property, two primary methods are part of the Promise object prototype; then() and catch(). When the pending Promise is fulfilled or rejected, the associated handlers with then() will be called. Both then() and catch() return promises, and they can be chained.

Let's start with a simple example. It could be found on the MDN.

let myFirstPromise = new Promise((resolve, reject) => {
    setTimeout(function(){
        resolve("Success!"); // fulfilled
    }, 250);
});

myFirstPromise.then((successMessage) => {
    console.log("Yay! " + successMessage);
});

Here we define a Promise, myFirstPromise, which will take an arrow function as the executer. The setTimeout(...) is to simulate async code which could be something like an xhr request. We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.

successMessage in then(...) is whatever we passed in the resolve(...) function above, and it doesn't have to be a string, but if it is only a succeed message, it probably will be.

Now a good example of using Promises is the fetch API that we used before when explaining the generators. The fetch API uses Promises internally, and we saw how we chained multiple then() there.

...
    fetch(url).then(data => data.json()).then(data => LukeSkywalker.next(data));
...

Let's take the example from the previous article and see how we can utilize it to handle nested promises.

const API = 'https://swapi.co/api/';

let LukeSkywalkerPromise = fetch(`${API}people/1/`);

LukeSkywalkerPromise
    .then(data => data.json())
    .then(data => {
        let LukeSkywalker = data;

        let LukeSkywalkerHomeworldPromise = fetch(data.homeworld);

        LukeSkywalkerHomeworldPromise
            .then(data => data.json())
            .then(data => {
                LukeSkywalker.homeworld = data;
                console.log(LukeSkywalker);
            })
            .catch((error) => { console.error(error); });
    })
    .catch((error) => { console.error(error); });

LukeSkywalkerPromise returns a Promise with data.json() Where we chain it and make the second promise to fetch the homeworld. The catch() methods will be executed when an error occurs.