Promise.allSettled() and its polyfill

Promise.allSettled() and its polyfill

Part 2 of the "Promise methods and their polyfills" series.

Part 1 of the series discussed the uses of the Promise.all() method and its polyfill. While there are multiple advantages of using it, there are certain limitations such as we will get a rejected promise in the event one of the promises in the array rejects. What if we want all the results and in the end want a fulfilled promise? Promise.allSettled() solves that issue.

This blog will introduce you to the method Promise.allSettled() and help you write its polyfill function.

Preface

It is assumed that you are comfortable with JavaScript and have knowledge about promises and the Promise.all() method. To recall what Promise.all() does, you can check out this link: Promise.all() and its polyfill

What is Promise.allSettled()

Let's directly jump to the code and see it in action

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise 1 resolved");
  }, 1000);
});

const promise2 = Promise.resolve("Promise 2 resolved");

const promise3 = 8;

const promiseAllSettled = Promise.allSettled([promise1, promise2, promise3]);

promiseAllSettled
  .then((result) => {
    console.log(result);
})
  .catch((err))=>{
    console.error(err)
});

Just like Promise.all(), Promise.allSettled() will take an array of promises as input and will run all the promises.

But in this case, we are asking for all the responses regardless of whether they are resolved or rejected. So when an individual promise in the array is resolved, we will get an object with properties status and value, and if it is rejected, we will get an object with properties status and reason. Also, the catch block in the above code will never execute because we will never get a rejected promise!

Output

image.png

As you can see, we get an array of objects, where the objects contain the responses from the resolved individual promises.

When one of the promises rejects

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise 1 resolved");
  }, 1000);
});

// instead of resolving, we reject promise 2
const promise2 = Promise.reject("Promise 2 reject"); 

const promise3 = 8;

const promiseAllSettled = Promise.allSettled([promise1, promise2, promise3]);

promiseAllSettled.then((result) => {
  console.log(result);
});

Output

image.png

Even after promise2 from the array was rejected, we will get an overall fulfilled promise with the responses.

Our implementation/polyfill of Promise.allSettled()

We want our polyfill to behave the same way as Promise.allSettled() does.

const promiseAllSettled = myPromiseAllSettled([promise1, promise2, promise3]);

promiseAllSettled.then((result) => {
  console.log(result); 
});
  • It should return a fulfilled promise with an array of objects as the result .
  • If one of the promises in the array is resolved, the property status should show fulfilled and value property should have the result.
  • If one of the promises in the array rejects, the property status should show rejected and the reason property should give us the error message/the reason why the promise was rejected.

myPromiseAllSettled() function

function myPromiseAllSettled(promisesArr) {
  let wrappedPromises = promisesArr.map((promise) =>

    // instead of doing value.then(), we have to use
    // Promise.resolve(value) so that we can convert
    // a non "promise" input into a "promise", and later
    // chain it with .then()
    Promise.resolve(promise)

      // when the promise is resolved, we return an object
      // with status and the value
      .then((val) => ({ status: "fulfilled", value: val }))

      // when the promise is rejected, we return an object
      // with the status and the reason
      .catch((err) => ({ state: "rejected", reason: err }))
  );

  // now the wrappedPromises array will contain all the settled promises
  // we will send this to Promise.all() which will resolve every time!
  return Promise.all(wrappedPromises); 
  // you can also use the polyfill from part 1 of the series and it'll work the same
}

The complete code

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise 1 resolved");
  }, 1000);
});

const promise2 = Promise.resolve("Promise 2 resolved");

const promise3 = 8;

function myPromiseAllSettled(promisesArr) {
  let wrappedPromises = promisesArr.map((promise) =>
    Promise.resolve(promise)
      .then((val) => ({ status: "fulfilled", value: val }))
      .catch((err) => ({ state: "rejected", reason: err }))
  );

  return Promise.all(wrappedPromises);
}
const promiseAllSettled = myPromiseAllSettled([promise1, promise2, promise3]);
promiseAllSettled.then((res) => console.log(res));
// [
//   { status: "fulfilled", value: "Promise 1 resolved" },
//   { status: "fulfilled", value: "Promise 2 resolved" },
//   { status: "fulfilled", value: 8 },
// ];

Conclusion

Promise.allSettled(promises) lets you run promises in parallel and collect the statuses (either fulfilled or reject) into an array.

Promise.allSettled(...) works great when you need to perform parallel and independent asynchronous operations and collect all the results even if some async operations could fail.