36Implement Promise.all() from scratch
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 timingRequirements
- Items may be promises, thenables or plain values (
promiseAll([1, Promise.resolve(2)])gives[1, 2]). - Accept any iterable, such as a
Setor 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.
Press Run or ⌘ Enter to execute your code.
Video walkthroughs
Stuck? Watch someone solve it, then try again on your own.
Step by step
Build the solution one idea at a time.
- 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
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
Handle the empty case
With zero items, resolve
[]immediately; otherwise the counter would never reach zero and the promise would hang. - 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
Store results by index
When an item fulfils, write its value to
results[index], notpush. Promises finish in any order, but the index keeps the output in input order. - 6
Count, do not check the array
Decrement a
remainingcounter on each fulfilment and resolve when it hits zero. Checkingresults.lengthwould not work, because writing to index 2 first already makes the length 3. - 7
Reject on the first failure
Pass
rejectas the rejection handler for every item. The first call settles the outer promise; later calls toresolveorrejectare 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.