Write your own version of Promise.all.
Return a new promise. Keep a results array and a counter. Wrap each input in Promise.resolve, store its value at its index, and resolve when the counter reaches zero. Reject as soon as any input rejects.
Explain it simply
Promise.all has three promises of its own to keep:
- Results come back in the same order as the input, so store each value by index, not by finishing order.
- It resolves only after all have fulfilled, so you need a counter of how many are still pending.
- It rejects the moment any rejects, which is what passing
rejectto eachthendoes.
Wrapping every item with Promise.resolve lets plain values like 2 sit in the array next to real promises. An empty array must resolve right away with an empty array.
Line by line
function myAll(items) {
return new Promise((resolve, reject) => {
const results = [];
let remaining = items.length;
if (remaining === 0) return resolve(results);
items.forEach((item, i) => {
Promise.resolve(item).then((value) => {
results[i] = value;
remaining--;
if (remaining === 0) resolve(results);
}, reject);
});
});
}
myAll([]).then((v) => console.log(v.length)); // Output: 0
const slow = new Promise((r) => setTimeout(() => r(3), 10));
myAll([Promise.resolve(1), 2, slow]).then((v) => console.log(v.join()));
// Output: 1,2,3What to say in the interview
"I return a new promise, wrap each item in Promise.resolve, store results by index to keep input order, decrement a counter on each success and resolve at zero. Passing reject to each then makes it fail fast. An empty input resolves immediately."
Complexity
Time O(n) to attach handlers, finishing when the slowest input settles. Space O(n) for the results array.
Common mistakes
- Using results.push, which returns values in completion order instead of input order.
- Forgetting the empty-array case, which would never resolve.