Promise.allSettled

Promise.allSettled returns a promise when all inputs are settled that is either fulfilled or rejected.

Points to remember:

  1. Returns a promise that contains an array of results.
  2. Each result object will have two properties (status & reason/value).
  3. status will be either fulfilled or rejected.
  4. value if fulfilled or a reason if rejected.

Difference between Promise.all & Promise.allSettled

Use cases:

I am not so sure of many use-cases. But, we want to know how it works right?. In the below example, first, we will see how it works and then will implement it.

var APICall1 = fetch('https://reqres.in/api/users?page=1');
var APICall2 = fetch('https://reqres.in/api/users?page=2');

Promise.allSettled([APICall1, APICall2]).then((results) => {
  results.forEach((result) => {
    console.log(result); // Result is below
  });
});

// Log:
// {status: "fulfilled", value: Response}
// {status: "fulfilled", value: Response}

Below is a simplified version of TC39 specs to understand its implementation details.

allSettled specification says

  1. Accepts iterable object as an argument.
  2. If not iterable, throw an exception with an error message.
  3. Iterate the argument if it is iterable.
  4. Keep the results in an array.
  5. Wait for all promises to either get resolved/rejected.
  6. Return the results.

First, we will write a function to find out if the passed value is iterable or not. We can use [Symbol.iterator] to find it out. Read more about the iterable here.

function isIterable(value) {
  // If no argument is passed or === null
  if (arguments.length === 0 || value === null) {
    return false;
  }

  return typeof value[Symbol.iterator] === 'function';
}

isIterable({}); // false
isIterable(1); // false
isIterable(nul); // false

isIterable([]); // true
isIterable(''); // true

// String, Array, TypedArray, Map & Set are all built-in iterables

Next step is to wrap our iteration (forEach) with new Promise() object. So that when all our inputs are settled, we can return the results with status and value/reasons.

Implementation is quite similar to Promise.all() with a few changes.

// Our custom all settled function
function isAllPromiseSettled(promises) {
  // To store our results
  var results = Array(promises.length);

  // To keep track of how many promises resolved
  var counter = 0;

  // If not iterable throw an error
  if (!isIterable(promises)) {
    throw new Error(`${typeof promises} is not iterable`);
  }

  // Wrapping our iteration with Promise object
  // So that we can resolve and return the results on done.
  return new Promise((resolve) => {
    // Iterate the inputs
    promises.forEach((promise, index) => {
      // Wait for each promise to resolve
      return Promise.resolve(promise)
        .then((result) => {
          counter++; // Increment counter

          // Store status and result in same order
          results[index] = { status: 'fulfilled', value: result };

          // If all inputs are settled, return the results
          if (counter === promises.length) {
            resolve(results);
          }
        })
        .catch((err) => {
          counter++; // Increment counter

          // Store status and reason for rejection in same order
          results[index] = { status: 'rejected', reason: err };

          // If all inputs are settled, return the results
          if (counter === promises.length) {
            resolve(results);
          }
        });
    });
  });
}

Conclusion

Yep, we did it. We learned what is Promise.allSettled() and implemented it. If you want to use it today, the browser support is pretty much available in all modern browsers.

The browser implementation might be different than ours. Nevertheless, you get the idea, right?

Thanks for reading till the end 😬. My next post is about Event Emitters (Pub/Sub). If you are not subscribed, subscribe below. See you next Tuesday ;)

Join 305+ Readers

Sent out once every month. No spam 🤞