JavaScript Promise API

As we have already stated, promises in JavaScript represent the eventual completion or failure of an asynchronous operation, as well as its resulting value.

The Promise class comprises five static methods. In this chapter, we cover all of them in detail.

Promise.all

The Promise.all method is used when it is necessary to execute many promises at the same time and wait until all of them are ready.

For example, you intend to download several URLs in parallel, processing the content when all of them are done.

The syntax of the Promise.all method is the following:

let promise = Promise.all([...promises...]);

It grabs an array of promises and returns a new promise. The new promise resolves at the time all the listed promises are settled. The array of their results becomes the result of it.

Let’s examine the example below:

Promise.all([
  new Promise(resolve => setTimeout(() => resolve(2), 3000)), // 2
  new Promise(resolve => setTimeout(() => resolve(4), 2000)), // 4
  new Promise(resolve => setTimeout(() => resolve(6), 1000)) // 6
]).then(console.log); // 2,4,6 when promises are ready: each promise is contributes an array member

Please, consider that the sequence of the resulting array members is equivalent to its source promises. Although the initial promise takes the longest time to resolve, it remains the first in the array of the results.

A usual trick is mapping an array of job data into an array of promises, and only then to wrap it into Promise.all.

In the example below, there is an array of URLs that is fetched as follows:

let urls = [
  'https://api.github.com/users/User1',
  'https://api.github.com/users/User2',
  'https://api.github.com/users/User3'
];
// map each url to the promise of the fetch
let requests = urls.map(url => fetch(url));
// Promise.all is waiting for all jobs to resolve
Promise.all(requests)
  .then(responses => responses.forEach(
    response => console.log(`${response.url}: ${response.status}`)
  ));

And, here is 1,2,3 when the promises are ready: each promise is contributed by an array member a larger example:

let names = ['User1', 'User2', 'User3'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
  .then(responses => {
    // all responses successfully resolved
    for (let response of responses) {
      console.log(`${response.url}: ${response.status}`); // shows 200 for every url
    }
    return responses;
  })
  // map the responses array to the response.json() array to read their content
  .then(responses => Promise.all(responses.map(res => res.json())))
  // all JSON answers are parsed: "users" is their array
  .then(users => users.forEach(user => console.log(user.name)));

In case any promise is rejected, the one that is returned by Promise.all rejects with that error at once.

For example:

Promise.all([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Error!!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(console.log); // Error: Error!!

In the example above, the second promise rejects in two seconds. It moves to the immediate rejection of Promise.all. Consequently, .catch executes, and the rejection error becomes the outcome of the whole Promise.all.

Note that, if an error occurs, other promises are ignored.

So, in case a promise rejects, Promise.all also rejects immediately, forgetting about the other promises in the list. Their result will be ignored, as well.

Imagine that you have multiple fetch calls, and one of them fails: the others will go on executing, but Promise.all will not look them up any longer. Probably, they will be settled, but their results will be ignored.

Promise.all(iterable) enables non-promise “regular” values inside an iterable.

Yet, of any of those objects is not a promise, it will pass to the resulting array “as is”.

Here is an example:

Promise.all([
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(2), 1000)
  }),
  4,
  6
]).then(console.log); // 2, 4, 6

Promise.allSettled

The Promise.allSettled method is used for returning a promise, which resolves after all of the given promises have resolved or rejected, along with an array of objects that each describe each promise’s outcome.

Let’s check out examples demonstrating the differences between the Promise.all and Promise.allSettled methods.

For instance, Promise.all rejects as a whole in case any of the promises rejects:

Promise.all([
  fetch('/index.html'),
  fetch('/style.css'),
  fetch('/data.json')
]).then(render); // the render method needs the results of all fatches
let urls = [
  'https://api.github.com/users/User1',
  'https://api.github.com/users/User2',
  'https://noSuchUrl'
];
Promise.allSettled(urls.map(url => fetch(url)))
  .then(results => { // (*)
    results.forEach((result, number) => {
      if (result.status == "fulfilled") {
        console.log(`${urls[number]}: ${result.value.status}`);
      }
      if (result.status == "rejected") {
        console.log(`${urls[number]}: ${result.reason}`);
      }
    });
  });

The result in the line (*) will be the following:

[{
    status: 'fulfilled',
    value: ...response...
  },
  {
    status: 'fulfilled',
    value: ...response...
  },
  {
    status: 'rejected',
    reason: ...error object...
  }
]

For each promise you will get its status and value/error.

Polyfill

In case your browser doesn’t support Promise.allSettled, you can polyfill it using this code:

if (!Promise.allSettled) {
  Promise.allSettled = function (promises) {
    return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({
      state: 'fulfilled',
      value
    }), reason => ({
      state: 'rejected',
      reason
    }))));
  };
}

Promise.race

Promise.race is similar to Promise.all. The difference is that it waits only for the initial promise settled and then gets either the result or the error of it.

Its syntax looks like this:

let promise = Promise.race(iterable);

Here, the result will be 1:

Promise.race([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Error!!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(console.log); // 1

So, in the example above, the initial promise was the fastest one; hence it became the result.

Promise.resolve/reject

Promise.resolve and Promise.reject methods are not used often in modern codes, as the async/await syntax makes them outdated. They are useful when it is not possible to use async/await.

Promise.resolve

This method is aimed at creating a resolved promise with the result value.

It’s the same as this:

let promise = new Promise(resolve => resolve(value));

This method is generally used for compatibility, whenever a function is expected to return a promise.

Promise Reject

The Promise.reject method is used for creating a rejected promise with an error.

It’s the same as:

let promise = new Promise((resolve, reject) => reject(error));

This method is almost never used in practice.

Summary

Promises allow associating handlers with an asynchronous action’s eventual success value or failure reason.

Promise class has five static methods: Promise.all(promises), Promise.allSettled(promises),Promise.race(promises), Promise.resolve(value) and promise.reject(error).

Of the mentioned above five methods, the most common in practice is Promise.all..




Do you find this helpful?

Related articles