FrontendPro

36Implement Promise.all() from scratch

mediumAbout 35 minutesCodingFreepromisesasyncpolyfills

Problem

Implement promiseAll(iterable) without using Promise.all. It returns a promise that:

  • fulfils with an array of every result, in the same order as the input, once all of them have fulfilled;
  • rejects as soon as any of them rejects, with that reason.
const user = fetchUser(); // resolves after 300 ms
const posts = fetchPosts(); // resolves after 100 ms
const [u, p] = await promiseAll([user, posts]); // order follows the input, not the timing

Requirements

  • Items may be promises, thenables or plain values (promiseAll([1, Promise.resolve(2)]) gives [1, 2]).
  • Accept any iterable, such as a Set or a generator.
  • An empty input fulfils with [].
  • Do not wait for the slow items once one has rejected.

Try it yourself

Write your solution, run it, then submit to check it against the tests.

solution.jsJavaScript

Press Run or ⌘ Enter to execute your code.

Video walkthroughs

Stuck? Watch someone solve it, then try again on your own.

Polyfill for Promise.all | SDE1 JavaScript interview question - 18Learnersbucket on YouTubeOpen on YouTube
25 Polyfill for promise methods all, allSettled, any & race | Javascript interview question in HindiCODERS NEVER QUIT on YouTubeOpen on YouTube

Step by step

Build the solution one idea at a time.

  1. 1

    Return a new promise

    Everything happens inside new Promise((resolve, reject) => …), so the caller always gets a promise, even for an empty input.

  2. 2

    Copy the iterable into an array

    Array.from(iterable) works for arrays, Sets and generators, and gives you a length to count down from.

  3. 3

    Handle the empty case

    With zero items, resolve [] immediately; otherwise the counter would never reach zero and the promise would hang.

  4. 4

    Normalise each item

    Promise.resolve(item) turns plain values and thenables into real promises, so you can treat every item the same way.

  5. 5

    Store results by index

    When an item fulfils, write its value to results[index], not push. Promises finish in any order, but the index keeps the output in input order.

  6. 6

    Count, do not check the array

    Decrement a remaining counter on each fulfilment and resolve when it hits zero. Checking results.length would not work, because writing to index 2 first already makes the length 3.

  7. 7

    Reject on the first failure

    Pass reject as the rejection handler for every item. The first call settles the outer promise; later calls to resolve or reject are ignored automatically.

Approach

Normalise every item with Promise.resolve, record each fulfilment at its own index, and resolve once a counter of outstanding items reaches zero. The first rejection settles the whole promise.

Solution

function promiseAll(iterable) {
  return new Promise((resolve, reject) => {
    const items = Array.from(iterable);
    const results = new Array(items.length);
    let remaining = items.length;

    if (remaining === 0) {
      resolve(results);
      return;
    }

    items.forEach((item, index) => {
      Promise.resolve(item).then((value) => {
        results[index] = value;
        remaining -= 1;
        if (remaining === 0) resolve(results);
      }, reject);
    });
  });
}

Complexity

Time: O(n) to subscribe to n items; the total wait is the time of the slowest item. Space: O(n) for the results.

Edge cases

  • promiseAll([]) fulfils with [] right away.
  • Plain values and thenables are treated as already-resolved or adopted promises.
  • The result order matches the input order, whatever the timing.
  • The first rejection wins; later rejections are ignored.
  • The other items keep running after a rejection: promises cannot be cancelled.

Follow-up questions

Answer each one out loud first, then reveal the model answer.