Polyfills for JavaScript Promise API: All 6 Static Methods

In modern JavaScript, the Promise API plays a crucial role in handling asynchronous operations. While most environments support these methods natively, understanding how to implement polyfills for these static methods is valuable, especially for older browsers or interview preparation. In this guide, we’ll walk through polyfills for all six Promise static methods: Promise.all, Promise.allSettled, Promise.race, Promise.any, Promise.resolve, and Promise.reject.

Polyfills for JavaScript Promise API: All 6 Static Methods

If you’re new to promises or want to brush up on their concepts, check out our comprehensive JavaScript Promises Guide and Promise API Overview. Additionally, for a deeper dive into custom Promise implementation, refer to Custom Promise Implementation in JavaScript.

Promise.all Polyfill:

The Promise.all method resolves when all promises in an iterable have resolved, or rejects as soon as one promise rejects.

if (!Promise.all) {
  Promise.all = function(promises) {
    return new Promise((resolve, reject) => {
      if (promises.length === 0) resolve([]); // Handle empty array case

      let resolvedCount = 0;
      const results = [];

      promises.forEach((promise, index) => {
        // Use Promise.resolve to handle non-promise values
        Promise.resolve(promise)
          .then(value => {
            resolvedCount++;
            results[index] = value;
            if (resolvedCount === promises.length) {
              resolve(results);
            }
          })
          .catch(reject); // Reject as soon as one promise rejects
      });
    });
  };
}

Note:

• If the input is an empty array, Promise.all should resolve immediately with an empty array.

• Non-promise values should be treated as resolved promises.

Promise.allSettled Polyfill:

Promise.allSettled resolves when all promises have settled, regardless of whether they were fulfilled or rejected.

if (!Promise.allSettled) {
  Promise.allSettled = function(promises) {
    return new Promise((resolve) => {
      if (promises.length === 0) resolve([]); // Handle empty array case

      let settledCount = 0;
      const results = [];

      promises.forEach((promise, index) => {
        // Use Promise.resolve to handle non-promise values
        Promise.resolve(promise)
          .then(value => {
            results[index] = { status: "fulfilled", value };
          })
          .catch(reason => {
            results[index] = { status: "rejected", reason };
          })
          .finally(() => {
            settledCount++;
            if (settledCount === promises.length) {
              resolve(results);
            }
          });
      });
    });
  };
}

Note:

• If the input is an empty array, Promise.allSettled should resolve immediately with an empty array.

• Non-promise values should be treated as resolved promises.

Promise.race Polyfill:

Promise.race resolves or rejects as soon as the first promise in the iterable settles.

if (!Promise.race) {
  Promise.race = function(promises) {
    return new Promise((resolve, reject) => {
      if (promises.length === 0) {
        return; // Leave the promise pending forever (or alternatively, do nothing)
      }

      promises.forEach(promise => {
        // Use Promise.resolve to handle non-promise values
        Promise.resolve(promise).then(resolve).catch(reject);
      });
    });
  };
}

Note: if the promises array is empty, the returned promise should never settle (resolve or reject). By not explicitly handling the empty array scenario within the promise, the behavior is consistent with the native Promise.race, which would leave the promise pending forever.

Promise.any Polyfill:

Promise.any resolves as soon as one of the promises resolves, or rejects with an AggregateError if all promises reject.

if (!Promise.any) {
  Promise.any = function(promises) {
    return new Promise((resolve, reject) => {
      if (promises.length === 0) {
        reject(new AggregateError([], "All promises were rejected")); // Handle empty array case
      }

      let rejectedCount = 0;
      const errors = [];

      promises.forEach(promise => {
        // Use Promise.resolve to handle non-promise values
        Promise.resolve(promise)
          .then(resolve)
          .catch(error => {
            rejectedCount++;
            errors.push(error);
            if (rejectedCount === promises.length) {
              reject(new AggregateError(errors, "All promises were rejected"));
            }
          });
      });
    });
  };
}

Note:

• If the input is an empty array, Promise.any should reject immediately with an empty AggregateError.

• Non-promise values should be treated as resolved promises.

Promise.resolve Polyfill:

Promise.resolve returns a promise that is resolved with a given value. If the value is a promise, it simply returns that promise.

if (!Promise.resolve) {
  Promise.resolve = function(value) {
    if (value instanceof Promise) {
      return value; // If the value is a promise, return it directly
    }
    return new Promise(resolve => {
      resolve(value);
    });
  };
}

Note: Non-promise values should be returned as resolved promises.

Promise.reject Polyfill:

Promise.reject returns a promise that is rejected with a given reason.

if (!Promise.reject) {
  Promise.reject = function(reason) {
    return new Promise((_, reject) => {
      reject(reason);
    });
  };
}

Common Interview Questions:

Understanding how to implement polyfills for Promise methods can help you answer interview questions such as:

• How would you implement a polyfill for Promise.all? (Hint: You need to handle multiple promises concurrently, resolving with an array of results when all succeed, and rejecting immediately if any fail. Consider handling non-promise values and an empty array.)

• What is the difference between Promise.all and Promise.allSettled, and how would you create a polyfill for each? (Hint: Promise.all rejects on the first failure, while Promise.allSettled waits for all to complete, regardless of success or failure. Consider using Promise.resolve to normalize values and handle both fulfilled and rejected outcomes.)

• Can you create a polyfill for Promise.any using basic promises? (Hint: Promise.any resolves on the first success and only rejects if all promises fail, using AggregateError. Think about counting rejections and handling an empty array input.)

Further References:

Implementing polyfills for the Promise API is a great way to deepen your understanding of promises and improve your coding skills. Whether you’re preparing for an interview or aiming to ensure compatibility across different environments, these polyfills provide a hands-on approach to mastering JavaScript promises.

🚀 If you enjoyed this post, why not show your love for coding with Javascript Developer Themed Tees from Usha Creations? Discover the perfect shirt for your next hackathon!

Reply

or to participate.