Implementing Promise.allSettled in JavaScript



Let’s dive into implementing Promise.allSettled, a method that was introduced in ECMAScript 2020. This method returns a promise that resolves after all of the given promises have either resolved or rejected, with an array of objects describing the outcome of each promise.


What is Promise.allSettled?

Promise.allSettled is used to handle multiple promises and obtain the result of each promise, regardless of whether it was resolved or rejected. Unlike Promise.all, which rejects as soon as one of the promises rejects, Promise.allSettled waits for all promises to settle and returns an array of objects describing the outcome of each promise.

Real Interview Insights

Interviewers might ask you to:

  • Implement Promise.allSettled from scratch.
  • Explain the differences between Promise.all and Promise.allSettled.
  • Discuss scenarios where Promise.allSettled is more appropriate than Promise.all.

Implementing Promise.allSettled

Here's a basic implementation of Promise.allSettled:

function promiseAllSettled(promises) {
  return new Promise((resolve) => {
    if (!Array.isArray(promises)) {
      throw new TypeError('Input must be an array');
    }
 
    const resultArray = [];
    let settledCounter = 0;
    const promisesCount = promises.length;
 
    if (promisesCount === 0) {
      return resolve([]);
    }
 
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(value => {
          resultArray[index] = { status: 'fulfilled', value };
        })
        .catch(reason => {
          resultArray[index] = { status: 'rejected', reason };
        })
        .finally(() => {
          settledCounter++;
          if (settledCounter === promisesCount) {
            resolve(resultArray);
          }
        });
    });
  });
}
Explanation:
  • Input Validation: Ensure the input is an array. If not, throw a TypeError.
  • Result Array: Use an array to store the result objects.
  • Settled Counter: Track the number of promises that have settled.
  • Promises Processing: For each promise, handle both fulfillment and rejection, and update the result array with appropriate status and value or reason.
  • Final Resolution: Resolve the main promise once all promises have settled.

Practical Example

Consider an example with a mix of resolved and rejected promises:

const promise1 = Promise.resolve(1);
const promise2 = Promise.reject('Error occurred');
const promise3 = Promise.resolve(3);
 
promiseAllSettled([promise1, promise2, promise3])
  .then(results => console.log(results));
// Output: 
// [
//   { status: 'fulfilled', value: 1 },
//   { status: 'rejected', reason: 'Error occurred' },
//   { status: 'fulfilled', value: 3 }
// ]

In this example:

  • The promiseAllSettled function handles each promise, regardless of its outcome, and returns an array describing the result of each promise.

Advanced Use Case: Handling Large Number of Promises

For scenarios with a large number of promises, performance considerations are important. The implementation provided is straightforward, but you should be aware of:

  • Memory Usage: Handling a large number of promises may consume significant memory.
  • Execution Time: Ensuring that the implementation is optimized for performance.

Coding Challenge: Promise.allSettled with Custom Settled Statuses

Challenge: Modify the promiseAllSettled function to include additional custom statuses or metadata for each promise.

function promiseAllSettled(promises) {
  return new Promise((resolve) => {
    if (!Array.isArray(promises)) {
      throw new TypeError('Input must be an array');
    }
 
    const resultArray = [];
    let settledCounter = 0;
    const promisesCount = promises.length;
 
    if (promisesCount === 0) {
      return resolve([]);
    }
 
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(value => {
          resultArray[index] = { status: 'fulfilled', value, timestamp: new Date().toISOString() };
        })
        .catch(reason => {
          resultArray[index] = { status: 'rejected', reason, timestamp: new Date().toISOString() };
        })
        .finally(() => {
          settledCounter++;
          if (settledCounter === promisesCount) {
            resolve(resultArray);
          }
        });
    });
  });
}
 
// Example usage with custom statuses
promiseAllSettled([promise1, promise2, promise3])
  .then(results => console.log(results));

In this challenge:

  • Enhance the promiseAllSettled function to include a timestamp or additional metadata for each promise result.