DEV Community

Cover image for Tutorial: Polyfill for Promise.allSettled, implementation from Scratch in JavaScript
mod khalid
mod khalid

Posted on

Tutorial: Polyfill for Promise.allSettled, implementation from Scratch in JavaScript

JavaScript introduced Promise.allSettled in ES2020 to make working with multiple asynchronous operations easier. Unlike Promise.all, which short-circuits when a promise rejects, Promise.allSettled ensures that you get results from all promises, whether they succeed or fail.

In this tutorial, I’ll walk you through creating your own implementation of Promise.allSettled, focusing on building it from scratch. We will also explore how promises work behind the scenes, helping you understand the asynchronous behavior that makes JavaScript so powerful.

What is Promise.allSettled?

Before we jump into writing code, let's break down what Promise.allSettled does:

  • Input: An array of promises.
  • Output: A promise that resolves after all input promises have settled (either fulfilled or rejected), with an array of objects describing the result of each promise.

Each object in the array contains:

  • status: "fulfilled" or "rejected".
  • value: The value if the promise resolved, or reason if it rejected. Example:
const promises = [
  Promise.resolve('Success'),
  Promise.reject('Failure'),
  Promise.resolve('Complete')
];

Promise.allSettled(promises).then(results => {
  console.log(results);
});
Enter fullscreen mode Exit fullscreen mode

Output:

[
  { status: 'fulfilled', value: 'Success' },
  { status: 'rejected', reason: 'Failure' },
  { status: 'fulfilled', value: 'Complete' }
]
Enter fullscreen mode Exit fullscreen mode

This method is ideal when you need to wait for all promises to finish, regardless of whether they succeed or fail.

Why Implement Promise.allSettled Yourself?

Even though this feature is now available in modern browsers, implementing it yourself offers a deeper understanding of how JavaScript promises work. Plus, it ensures compatibility with older environments that don’t support ES2020 features natively.

Step-by-Step Guide to Implement Promise.allSettled

We’re going to create a function named allSettled that mimics the behavior of Promise.allSettled. Let’s build this step-by-step:

Step 1: Create a Wrapper Function

The function takes an array of promises and returns a new promise. This new promise will resolve when all the input promises settle (either resolve or reject).

function allSettled(promises) {
  // This will return a new promise that resolves when all promises settle
  return new Promise((resolve) => {
    // Implementation goes here
  });
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Handle Each Promise

For each promise in the array, we need to track whether it resolves or rejects. We’ll wrap each promise with a .then() and .catch() to capture its status:

function allSettled(promises) {
  return new Promise((resolve) => {
    let results = [];
    let count = 0;

    promises.forEach((promise, index) => {
      // Wrap the promise with a .then() and .catch() to capture the result
      promise
        .then((value) => {
          results[index] = { status: 'fulfilled', value };
        })
        .catch((reason) => {
          results[index] = { status: 'rejected', reason };
        })
        .finally(() => {
          count++;
          // Once all promises have settled, resolve the outer promise
          if (count === promises.length) {
            resolve(results);
          }
        });
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

1. Creating the Outer Promise:

The allSettled function returns a new Promise. This promise will resolve when all input promises have settled.

2. Looping Over Promises:

We loop through the promises array using .forEach. For each promise, we track its outcome with .then() (for resolved promises) and .catch() (for rejected promises).

3. Recording Results:

The result of each promise is stored in the results array. If the promise resolves, the object contains { status: 'fulfilled', value }. If it rejects, it stores { status: 'rejected', reason }.

4. Counting Settled Promises:

We use a count variable to track how many promises have settled. Each time a promise finishes (either through .then() or .catch()), we increment the count. Once count equals the length of the input array, we resolve the outer promise with the results array.

Step 3: Testing Our allSettled Function

Now, let's test this custom implementation:

const promises = [
  Promise.resolve('Task 1 completed'),
  Promise.reject('Task 2 failed'),
  Promise.resolve('Task 3 completed')
];

allSettled(promises).then((results) => {
  console.log(results);
});
Enter fullscreen mode Exit fullscreen mode

Output:

[
  { status: 'fulfilled', value: 'Task 1 completed' },
  { status: 'rejected', reason: 'Task 2 failed' },
  { status: 'fulfilled', value: 'Task 3 completed' }
]
Enter fullscreen mode Exit fullscreen mode

As expected, the function waits for all promises to settle, and the results array gives us detailed information about each promise, including whether it resolved or rejected.

A Deeper Look Into Promises

To reinforce understanding, let's break down how promises work:

  • Promises represent the eventual result of an asynchronous operation. When a promise is created, it is in a pending state.
  • A promise can be:

Resolved (fulfilled) when the operation completes successfully.

Rejected when the operation fails.

Settled when it is either fulfilled or rejected

The then() method allows us to specify a function to be executed when the promise is resolved, while catch() allows us to handle rejections. Using finally() ensures that we handle the promise settling (either success or failure) without duplication.

Conclusion

Implementing Promise.allSettled yourself is a fantastic way to understand how promises work at a fundamental level. The key takeaway is that Promise.allSettled waits for all promises to finish, unlike Promise.all, which stops when it encounters a rejection.

By implementing this from scratch, you’ve also learned how to handle multiple asynchronous operations in a clean and efficient way. Now you can use this knowledge to work with promises in any JavaScript environment, including those that don’t natively support modern features.

Top comments (0)