DEV Community

ZeeshanAli-0704
ZeeshanAli-0704

Posted on • Edited on

Polyfill - Promise.all

Build Your Own Promise.all (Step‑by‑Step Guide with a Polyfill)

When you need to run multiple asynchronous tasks in parallel and wait for all of them to finish, Promise.all is your go‑to. In this post, we’ll build a simple polyfill called Promise.myAll, explain how it works, cover edge cases, and test it with a few sample promises.

What Promise.all Does (in a nutshell)

  • Takes an iterable of values (Promises or non-Promises).
  • Resolves with an array of results when all inputs resolve.
  • Rejects immediately if any input rejects (first rejection wins).
  • Preserves the original order of results, regardless of completion times.

Baseline: Sample Promises

const prom1 = new Promise((resolve) => {
  setTimeout(() => resolve("gfg1"), 1000);
});

const prom2 = new Promise((_, reject) => {
  setTimeout(() => reject("error"), 2000);
});

const prom3 = new Promise((resolve) => {
  setTimeout(() => resolve("gfg2"), 3000);
});

const prom4 = new Promise((resolve) => {
  setTimeout(() => resolve("gfg3"), 3000);
});
Enter fullscreen mode Exit fullscreen mode

First Attempt: A Working Promise.all Polyfill

Here’s a minimal implementation, close to what you shared, with clean formatting and comments:

Promise.myAll = function (values) {
  return new Promise((resolve, reject) => {
    // Handle empty array or non-array: normalize input
    if (!values || values.length === 0) {
      return resolve([]);
    }

    const results = new Array(values.length);
    let settledCount = 0;
    let done = false; // prevent multiple resolve/reject

    values.forEach((item, index) => {
      Promise.resolve(item)
        .then((val) => {
          if (done) return;
          results[index] = val;    // preserve order by index
          settledCount += 1;
          if (settledCount === values.length) {
            done = true;
            resolve(results);
          }
        })
        .catch((err) => {
          if (done) return;
          done = true;
          reject(err);             // first rejection wins
        });
    });
  });
};
Enter fullscreen mode Exit fullscreen mode

What’s happening here

  • Promise.resolve(item) ensures we handle both Promises and plain values.
  • We store each resolved value by its original index to preserve order.
  • We count how many have resolved; when all are done, resolve with the results array.
  • If any rejects, we immediately reject and ignore future settlements (first rejection wins).
  • We also handle empty input upfront by resolving to [].

Why the empty array guard matters

In many naive implementations, passing [] would never resolve because the forEach never runs and the resolver is never called. The guard fixes that.

Testing Our Implementation

1) Case with a rejection

Promise.myAll([prom1, prom2, prom3])
  .then((res) => {
    console.log("Resolved:", res);
  })
  .catch((err) => {
    console.log("Rejected:", err);
  });
Enter fullscreen mode Exit fullscreen mode

Expected output (after ~2s):

Rejected: error
Enter fullscreen mode Exit fullscreen mode

2) All resolve

Promise.myAll([prom1, prom3, prom4])
  .then((res) => {
    console.log("Resolved:", res);
  })
  .catch((err) => {
    console.log("Rejected:", err);
  });
Enter fullscreen mode Exit fullscreen mode

Expected output (after ~3s):

Resolved: [ 'gfg1', 'gfg2', 'gfg3' ]
Enter fullscreen mode Exit fullscreen mode

3) Mixed with plain values (still works)

Promise.myAll([42, Promise.resolve("ok"), "plain"])
  .then(console.log) // [42, 'ok', 'plain']
  .catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Optional: Support Any Iterable (Not Just Arrays)

If you want to support any iterable and still preserve order:

Promise.myAll = function (iterable) {
  return new Promise((resolve, reject) => {
    const items = Array.from(iterable); // normalize
    const len = items.length;

    if (len === 0) return resolve([]);

    const results = new Array(len);
    let settledCount = 0;
    let done = false;

    for (let i = 0; i < len; i++) {
      Promise.resolve(items[i])
        .then((val) => {
          if (done) return;
          results[i] = val;
          settledCount++;
          if (settledCount === len) {
            done = true;
            resolve(results);
          }
        })
        .catch((err) => {
          if (done) return;
          done = true;
          reject(err);
        });
    }
  });
};
Enter fullscreen mode Exit fullscreen mode

Behavior Notes and Pitfalls

  • Order is preserved: Results appear in the same order as inputs, not by completion time.
  • Early rejection: The first rejection triggers the overall rejection. Other promises continue running in the background—JavaScript doesn’t cancel them automatically.
  • Non-Promise values: They’re wrapped via Promise.resolve and treated as already-resolved.
  • Empty input: Should resolve immediately to [] (guard included above).
  • Error handling: If you need to collect all outcomes regardless of errors, use Promise.allSettled or implement a similar pattern.

Performance Snapshot

  • Time: O(n) orchestrating promise settlements.
  • Space: O(n) to store results.

Summary

  • We implemented Promise.myAll with proper order preservation and early rejection.
  • We handled edge cases like empty inputs and non-Promise values.
  • Use it as a learning tool; in production, prefer the native Promise.all when available.

Top comments (0)