The Promise API in Javascript

·

4 min read

Pre-Requisites

  • JS Fundamentals.
  • Familiarity with the basics of Asynchronous nature of Javascript.
  • Async/await syntax

Introduction - Refresher on Promises.

Let us go through what MDN says first:

A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.*

Constructor syntax for promises:

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

The three states of promises:

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

The 4 main methods of Promises.

We'll understand all the methods with the help of both code, and an interesting examples. Let's say you are a group of friends who have promised each other to go to a restaurant for lunch.

Promise.all

Now, Promise.all is a condition wherein you'll go for lunch only when each and every member of the friend circle is willing to go. That is, no one "rejects" your offer to go to a restuarant.

Similarly, Promise.all takes an interable, usually an array of promises and returns a new promise. However, if any of the promises inside the iterable is rejected, then the Promise returned by Promise.all is also rejected.

Let's understand this with the help of an example:

Promise.all([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
  new Promise(resolve => setTimeout(() => resolve(3), 1000))  // 3
]).then(console.log("resolved");

Note: The responses by each of the promises in promise.all is collected in an array of the results of the respective promises. Also, the order of the results in the array are the same order in which the promises were executed.

Promise.allSettled

Promise.all was the case where even if one of the friends denied the proposal for going to the restaurant, no one did. On the other hand, Promise.allSettled is a scenario wherein some of the friends want to go out for lunch but some don't. Then the group splits into two and live in harmony.

Promise.allSettled is analogous to this situation. Promise.allSettled waits for all the promises to settle, regardless of the result. The resulting array from the settlement has two things.

  • {status:"fulfilled", value:result} for successful responses,
  • {status:"rejected", reason:error} for errors.
let urls = [
  'https://valid-url',   //acually gives response
  'https://valid-url',  //actually gives response
  'https://no-such-url' //results in error
];

Promise.allSettled(urls.map(url => fetch(url)))
  .then(results => { 
          console.log('Result', results);           
    });
  });

The result from above will of the following kind

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

Hence in this case, even if some promises are rejected, we get the result of the ones that were not. This gives us a more holistic and detailed insights. While Promise.all was a zero-one game, Promise.allSettled is more of the 50shades kind.

Promise.race

Promise.race is a situation wherein the friend that reaches the restaurant first is the only one to have lunch.

Promise.race also takes in an array of promises just like Promise.all or Promise.allSettled, but it only waits for the first settled promised, fulfilled or rejected.

Promise.race([
  new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert);

In the above scenario, since the second promise will resolve first, hence it will be the result. Once result from the promise that "won-the-race" is returned, the results, success or error are ignored.

Promise.any

It is similar to Promise.race, but it only waits for the first promise that is fulfilled. If none of the promises are fulfilled then the returned promise is rejected with AggregateError – a special error object that stores all promise errors in its errors property.

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

The first promise here was fastest, but it was rejected, so the second promise became the result. After the first fulfilled promise “wins the race”, all further results are ignored.

Conclusion

You can leverage all the above methods that the Promise API provides and make your code more concise. Each of the API methods fit in a particular situation, look for patterns and it'll make your life easier.

References