Promises in JavaScript Promises in JavaScript

Promises are introduced natively in ES6. They are very similar to our promises. As we keep or break our promises, Javascript promises are also either resolve or reject. In this post we will talk about why we should use promises, promise syntax, promise states and its practical usage with examples using fetch API.

Why Promises?

In earlier days, you would have used callbacks to handle asynchronous operations. However, callbacks have limited functionality and often leads to unmanageable code if you are handling multiple async calls, it leads to heavily nested callback code which is also known as callback hell.

Promises were introduced to improve code readability and better handling of async calls and errors.

Promise Syntax

Let’s look at the syntax of simple promise object.

let promise = new Promise(function (resolve, reject) {
  // asynchronous call
});

Promise takes a callback function as an argument and that callback function takes two arguments — the first is a resolve function, and the second one is a reject function. A promise can either be fulfilled with a value or rejected with a reason (error).

Promise States

A promise object has one of three states:

  • pending: is the initial state.
  • fulfilled: is success state. resolve() method is called.
  • rejected: is failed state, reject() is called.

Usage

We generally use async calls using fetch API to get data from the HTTP endpoints. Let’s look at the example, how promises can be used in such case.

//create a promise object
let getUsers = new Promise(function (resolve, reject ){
    fetch('https://jsonplaceholder.typicode.com/users')
    .then(response => {
        return response.json();
    })
    .then(data => {
        resolve(data);
    })
    .catch(error => {
        reject(error);
    });
});

//call promise object
getUsers
.then((data) => {
  console.log(data);
})
.catch(error => {
  console.log(error);
});

Chained Promises

Promise chaining comes into play when you have to use output of one async call as input of another async call. You can chain multiple promises in this case.

Let’s look at the below example where we first fetch users list using getUser async call then chain it with getPosts by passing userId.

//create a common getData function
let getData = (url) => new Promise(function (resolve, reject ){
  fetch(url)
    .then(response => {
        return response.json();
    })
    .then(data => {
        resolve(data);
    })
    .catch(error => {
        reject(error);
    });
});

let getUsers = getData('https://jsonplaceholder.typicode.com/users');
let getPosts = (userId) => getData(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`);

//chained promises to fetch all posts by first user (userId = 1)
getUsers.then((data) => {
  const user = data[0];
  return getPosts(user.id);
})
.then((data) => {
  console.log(data);
})
.catch(error => {
  console.log(error);
});
Output
▼ (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] ➤ 0: {userId: 1, id: 1, title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body: "quia et suscipit↵suscipit recusandae consequuntur …strum rerum est autem sunt rem eveniet architecto"} ➤ 1: {userId: 1, id: 2, title: "qui est esse", body: "est rerum tempore vitae↵sequi sint nihil reprehend…aperiam non debitis possimus qui neque nisi nulla"} ➤ 2: {userId: 1, id: 3, title: "ea molestias quasi exercitationem repellat qui ipsa sit aut", body: "et iusto sed quo iure↵voluptatem occaecati omnis e…↵molestiae porro eius odio et labore et velit aut"} ➤ 3: {userId: 1, id: 4, title: "eum et est occaecati", body: "ullam et saepe reiciendis voluptatem adipisci↵sit … ipsam iure↵quis sunt voluptatem rerum illo velit"} ➤ 4: {userId: 1, id: 5, title: "nesciunt quas odio", body: "repudiandae veniam quaerat sunt sed↵alias aut fugi…sse voluptatibus quis↵est aut tenetur dolor neque"} ➤ 5: {userId: 1, id: 6, title: "dolorem eum magni eos aperiam quia", body: "ut aspernatur corporis harum nihil quis provident …s↵voluptate dolores velit et doloremque molestiae"} ➤ 6: {userId: 1, id: 7, title: "magnam facilis autem", body: "dolore placeat quibusdam ea quo vitae↵magni quis e…t excepturi ut quia↵sunt ut sequi eos ea sed quas"} ➤ 7: {userId: 1, id: 8, title: "dolorem dolore est ipsam", body: "dignissimos aperiam dolorem qui eum↵facilis quibus…↵ipsam ut commodi dolor voluptatum modi aut vitae"} ➤ 8: {userId: 1, id: 9, title: "nesciunt iure omnis dolorem tempora et accusantium", body: "consectetur animi nesciunt iure dolore↵enim quia a…st aut quod aut provident voluptas autem voluptas"} ➤ 9: {userId: 1, id: 10, title: "optio molestias id quia eum", body: "quo et expedita modi cum officia vel magni↵dolorib…it↵quos veniam quod sed accusamus veritatis error"} length: 10 ➤ __proto__: Array(0)

Promise.all()

Promise.all() is useful when you want to execute multiple async calls and wait for all of them to finish and get collective output.

Promise.all() takes an array of promises and return an array of the results in the same sequence of promises. It throws an exception if any one of the async call fails.

Let’s look at the below example, where we fetch the result of three async calls getUsers, getPosts and getComments all together.

//create a common getData function
let getData = (url) => new Promise(function (resolve, reject ){
  fetch(url)
    .then(response => {
        return response.json();
    })
    .then(data => {
        resolve(data);
    })
    .catch(error => {
        reject(error);
    });
});

//create multiple promises from common getData function
let getUsers = getData('https://jsonplaceholder.typicode.com/users');
let getPosts = getData('https://jsonplaceholder.typicode.com/posts');
let getComments = getData('https://jsonplaceholder.typicode.com/comments');

//fetch data to get users, posts and comments collectively
Promise.all([getUsers, getPosts, getComments]).then(result => {
  console.log(result);
});
Output
▼ (3) [Array(10), Array(100), Array(500)] ➤ 0: (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] ➤ 1: (100) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] ➤ 2: (500) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …] length: 3 ➤ __proto__: Array(0)

Promise.allSettled()

Promise.allSettled() is similar to Promise.all() to execute multiple async calls. The only difference between these two,

  • Promise.all() is either all resolve or any reject, means if any async call fails, it returns an error.
  • Promise.allSettled() is all settle, means it doesn’t return error if any async call fails. It gives the collective output of all successful and failed async calls.

Let’s look at the below example, where getPostsFails aync call returns 404 error since url endpoint doesn’t exist but you still able to fetch data for getUsers and getComments async calls.

//create a common getData function
let getData = (url) => new Promise(function (resolve, reject ){
  fetch(url)
    .then(response => {
        if(response.ok){
          return response.json();
        }else{
          throw `Error ${response.status}`;
        }     
    })
    .then(data => {
        resolve(data);
    })
    .catch(error => {
        reject(error);
    });
});

//create multiple promises from common getData function
let getUsers = getData('https://jsonplaceholder.typicode.com/users');
let getPostsFails = getData('https://jsonplaceholder.typicode.com/postsfailes');
let getComments = getData('https://jsonplaceholder.typicode.com/comments');

//fetch data to get users, posts and comments collectively regardless of any error
Promise.allSettled([getUsers, getPostsFails, getComments]).then(result => {
  console.log(result);
});
Output
▼ (3) [{…}, {…}, {…}] ➤ 0: {status: "fulfilled", value: Array(10)} ➤ 1: {status: "rejected", reason: "Error 404"} ➤ 2: {status: "fulfilled", value: Array(500)} length: 3 ➤ __proto__: Array(0)

Promise.race()

Promise.race() is useful when you are interested in fetching data from any one of the async call out of multiple async calls, whichever resolve first.

Look at the below example where we are interested in any data out of getTodos, getUsers and getComments, whichever resolves first. In our case getUsers resolved first and returned user list.

Please note that if you execute the same code snippet again and again, result might differ based on network connectivity and which one resolve first.

//create a common getData function
let getData = (url) => new Promise(function (resolve, reject ){
  fetch(url)
    .then(response => {
        return response.json();
    })
    .then(data => {
        resolve(data);
    })
    .catch(error => {
        reject(error);
    });
});

//create multiple promises from common getData function
let getTodos = getData('https://jsonplaceholder.typicode.com/todos');
let getUsers = getData('https://jsonplaceholder.typicode.com/users');
let getComments = getData('https://jsonplaceholder.typicode.com/comments');


//fetch either todos or users or comments whichever resolves first
Promise.race([getTodos, getUsers, getComments]).then(result => {
  console.log(result);
});
Output
▼ (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] ➤ 0: {id: 1, name: "Leanne Graham", username: "Bret", email: "Sincere@april.biz", address: {…}, …} ➤ 1: {id: 2, name: "Ervin Howell", username: "Antonette", email: "Shanna@melissa.tv", address: {…}, …} ➤ 2: {id: 3, name: "Clementine Bauch", username: "Samantha", email: "Nathan@yesenia.net", address: {…}, …} ➤ 3: {id: 4, name: "Patricia Lebsack", username: "Karianne", email: "Julianne.OConner@kory.org", address: {…}, …} ➤ 4: {id: 5, name: "Chelsey Dietrich", username: "Kamren", email: "Lucio_Hettinger@annie.ca", address: {…}, …} ➤ 5: {id: 6, name: "Mrs. Dennis Schulist", username: "Leopoldo_Corkery", email: "Karley_Dach@jasper.info", address: {…}, …} ➤ 6: {id: 7, name: "Kurtis Weissnat", username: "Elwyn.Skiles", email: "Telly.Hoeger@billy.biz", address: {…}, …} ➤ 7: {id: 8, name: "Nicholas Runolfsdottir V", username: "Maxime_Nienow", email: "Sherwood@rosamond.me", address: {…}, …} ➤ 8: {id: 9, name: "Glenna Reichert", username: "Delphine", email: "Chaim_McDermott@dana.io", address: {…}, …} ➤ 9: {id: 10, name: "Clementina DuBuque", username: "Moriah.Stanton", email: "Rey.Padberg@karina.biz", address: {…}, …} length: 10 ➤ __proto__: Array(0)

Summary

It is always better to use Promises over callback functions as Promises has a lot to offer in terms of,

  • Resolve a successful anyc call response using Promise.resolve(response)
  • Reject async call response based on status or data using Promise.reject(response)
  • Better error handling using Promise.catch(onRejection)
  • Chaining of multiple async calls using Promise.then(onFulfillment, onRejection)
  • Get collective result of multiple async calls using Promise.all([promise1, promise2, ...])
  • Get collective result of multiple async calls regardless of any error using Promise.allSettled([promise1, promise2, ...])
  • Get any one result whichever resolves first out of many async calls using Promise.race([promise1, promise2, ...])