What is a Promise in JavaScript?

A promise is an object which handles asynchronous requests. When implementation is correct, it will always promise that you will get a single value (either success data or error data) in the future. With the help of promise, you can manage asynchronous requests in a clean way as compare to callback functions.

It can be in any of the three states. These states are Pending, Fulfilled or Rejected. Through its entire life-cycle it can be in only two states, first is Pending state and second will be either Fulfill or Reject state.

  • Pending: When a promise is initialized using executor function.
  • Fulfilled: When an asynchronous request is successful.
  • Rejected: When some error occurs like the error is thrown during code execution or due to network error.

Syntax of a Promise

new Promise(executor)
  .then(success => {
    // When task completes.
  })
  .catch(error => {
    //  When some error occurs and task fails.
  }); // for details check promise implementation section on this page.

Once the promise has Fulfilled or Rejected, it cannot switch to any other state. When we are saying that promise has settled then we mean that it has reached Fulfilled or Rejected state.

JavaScript does not expose the state of promises. But instead, we should treat it as a black box. Only the function responsible for creating the promise will have knowledge of the state of promise. When promise settles, it will inform about settled state by executing callbacks. The callback functions are success callback and error callback.

How to implement a Promise in JavaScript?

1) Initialize a Promise.

let promise = new Promise(function(resolve, reject) {
  // this is executor function
  setTimeout(function() {
    resolve("kudos");
  }, 3000);
});

Executor function has resolve and reject arguments which are callbacks. Whichever argument executes first, the promise settles to that state which is the final state.

When promise resolves and if we need to return the result then we should pass the return value to resolve() callback as argument. Also in order to pass return value when promise rejects then we have to pass return value to reject() callback.

2) Attach success and error callbacks.

// this example is continuation of point 1 of "How to implement Promise?" section.

promise
  .then(function(data) {
    // this is success callback
    console.log("promise resolved. " + data);
  })
  .catch(function(err) {
    // this is error callback
    console.log("promise rejected with error: " + err);
  });

In the executor function, resolve callback indicates success which results in the execution of then callback. Execution of reject callback indicates an error which will run catch callback.

We can reject promise programmatically or can also reject automatically. Reasons for rejection can be a system error or network error. In above example we can reject promise by replacing resolve('kudos') with reject('sad'). So after the code modification error callback will run.

3) When promise settles?
It will either execute a success function or error handling function.

Javascript promise settlement
The image shows how control flows when promise settles to different states. Here promises are chained in series. (image source)

Another way to implement a promise

let fetchVehiclesPromise = new Promise(function(resolve, reject) {
  // this is executor function
  setTimeout(function() {
    resolve("alternative way to define promise");
  }, 2000);
});

let successCallback = data => {
  // this is success callback
  console.log(data);
};

let errorCallback = err => {
  // this is error callback
  console.log("fetching vehicles failed");
};

fetchVehiclesPromise.then(successCallback, errorCallback);

The above code represents an alternative way to define promise. This code differs from the previous implementation only by specifying success callback and error callback to then method. In the above code errorCallback handles errors thrown by fetchVehiclesPromise and errorCallback will not handle any errors thrown in successCallback function.

So the disadvantage of this implementation is that if error is thrown in success callback then we do not have any function to handle the error 🙁

Browser Support

Native support for promise in JavaScript is from ECMAScript 2015. To check the browser compatibility for various promise methods across some major browsers then click HERE.

Benefits of using Promises

  • Better management of asynchronous requests.
  • Improves code readability.
  • Improves error handling.

Warnings while implementing

  • Success handling and error handling functions are compulsory. If we do not handle both the states then in future JavaScript versions, JavaScript will throw error.
  • Do not mix callback functions with promises.
  • Avoid nesting of promises which will make code debugging and code review a bit difficult. Refer example given below.
getMembersFromAPI()
  .then(apiMembers =>
    getMembersFromDB(apiMembers)
      .then(members =>
        addBonusToMembers(members).then(data => {
          console.error("add bonus resolved");
        })
      )
      .catch(err => console.error("add bonus failed: " + err))
  )
  .then(data => checkForMembers())
  .catch(err => console.error("Error occurred: " + err));

Shortcut to Settle a promise

Promise.resolve() and Promise.reject() are shortcuts to create an already resolved or rejected promise respectively.

Check the below examples.

let shortcuts_res = Promise.resolve("passed");

shortcuts_res
  .then(value => console.log("Promise is resolved"))
  .catch(err => console.log("Promise is rejected"));

let shortcuts_rej = Promise.reject("failed");

shortcuts_rej
  .then(value => console.log("Promise is resolved"))
  .catch(err => console.log("Promise is rejected"));

Composition tools

Promise.all() and Promise.race() are two composition tools. With the help of these tools, we can execute multiple promises in parallel. Now we will check the implementation of both tools.

1. Promise.all()

If there is a case where we need to fetch data from multiple APIs and process them only if all calls are successful then Promise.all() is a good option.

Consider the following example.

let promiseA = Promise.resolve("A");

let promiseB = Promise.resolve("B");

let promiseC = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, "C");
});

Promise.all([promiseA, promiseB, promiseC])
  .then(([resultA, resultB, resultC]) => {
    console.log("result for promise " + resultA);
    console.log("result for promise " + resultB);
    console.log("result for promise " + resultC);
  })
  .catch(err => {
    console.log("promise " + err + " failed.");
  });

In this example, there is no specific execution order of promiseA, promiseB, promiseC promises. The then callback will be executed only when all promises are resolved or else catch callback will be called. If any one promise fails then callback function will execute and promise will settle.

2. Promise.race()

From the list of asynchronous requests, if we want the result of the first operation which settles to resolve or reject state then Promise.race() will do work for us.

Consider the following example.

let promiseA = new Promise(function(resolve, reject) {
  setTimeout(resolve, 400, "A");
});

let promiseB = new Promise(function(resolve, reject) {
  setTimeout(resolve, 300, "B");
});

Promise.race([promiseA, promiseB])
  .then(value => console.log("Promise " + value + " is resolved"))
  .catch(err => console.log("Promise " + err + " is rejected"));

Here we are defining two promises promiseA and promiseB. For promiseA, A is argument to resolve callback. For promiseB, B is argument to resolve callback. In this example, promiseB will be resolved first, so then callback will be executed using promiseB result. You can experiment by interchanging settled states.

Chaining of Promises

Good way to execute Async operations in series is chaining all operations by using promises. In order to implement chaining ‘then’ callback should return a promise which will get handled by subsequent callbacks.

In order to handle errors, we can add catch at last after all then callback or we can add catch after then callback.

let chainA = function() {
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      resolve("chainA");
    }, 1000);
  });
};

let chainB = function() {
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      resolve("chainB");
    }, 3000);
  });
};

let chainC = function() {
  return Promise.reject("Error in chainC");
};

chainA()
  .then(data => {
    console.log(data);
    return chainB();
  })
  .then(data => {
    console.log(data);
    return chainC();
  })
  .then(data => {
    console.log(data);
    console.log("all async request were successfull");
  })
  .catch(err => {
    console.log("common error handle");
    console.log("all async request were not successfull with error " + err);
  });

Above example of chaining promises and handling errors with a single catch at last. Here it shows, how to do our tasks in series and chaining of promises is a perfect example of it. In order to chain all tasks, each task should return a promise. There should be an error handler function which is defined at last in the chaining so that the last error handler deals with any error that go unnoticed. Check the example and also experiment with it.

The below example illustrates how we can add a handler for each specific task that fails. Also, check comments in code.

let chainA = function() {
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      resolve("chainA");
    }, 1000);
  });
};

let chainB = function() {
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      reject("chainB");
    }, 3000);
  });
};

let chainC = function() {
  return Promise.reject("Error in chainC");
};

chainA()
  .then(
    data => {
      // success handler for chainA
      console.log(data);
      return chainB().catch(err => {
        // error handler for chainB
        console.log("Error in " + err);
        throw new Error(err);
      });
    },
    err => {
      // error handler for chainA
      console.log("Error in " + err);
      throw new Error(err);
    }
  )
  .then(data => {
    // success handler for chainB
    console.log(data);
    return chainC().catch(err => {
      // error handler for chainC
      console.log("Error in " + err);
      throw new Error(err);
    });
  })
  .then(data => {
    // success handler for chainC
    console.log(data);
    console.log("all async request were successfull");
  })
  .catch(err => {
    // common error handler for all tasks
    console.log("all async request were not successfull with error " + err);
  });

Things to note about Promises

To reject a promise we can call reject function which is an argument to executor function or else we can throw an error in executor function. Whichever definition is first, the promise will then settle to that particular value.

let promise = new Promise(function(resolve, reject) {
  throw new Error("calcution error"); // throw will reject promise
  reject("Reject!");
});

promise
  .then(function(value) {
    console.log(value);
  })
  .catch(function(err) {
    console.log(err);
  });

In the above code example, the promise is going to be rejected by throwing an error.

Download examples based on Promise

All the examples used in this article are available for download on GitHub. Please give me a star if you like them.

Further reading: I strongly recommend you to read the article on Complete list of ES2017 (ES8) Features!, specifically the Async and Await part of it.

Please share your thoughts in the comments below. You can also share your feedback on this article.