Building Promise.all
from scratch
What happens when you await in a loop vs running promises concurrently? A visual deep-dive into one of JavaScript's most misunderstood patterns.
Sequential vs concurrent execution
Watch 5 promises race. The sequential version awaits each one before starting the next. The concurrent version fires them all at once. Click run and see the difference.
My implementation
No libraries. No cheating. Just the Promise constructor, a counter, and careful index tracking.
export function promiseAll<T>(promises: (T | Promise<T>)[]): Promise<T[]> {
return new Promise((resolve, reject) => {
const result: T[] = [];
let counter = 0;
const total = promises.length;
// Edge case: empty array resolves immediately
if (total === 0) {
resolve([]);
return;
}
promises.forEach((promise, index) => {
// Wrap with Promise.resolve to handle non-promise values
Promise.resolve(promise)
.then((res) => {
result[index] = res; // index preserves order
counter++; // counter tracks completion
if (counter === total) {
resolve(result);
}
})
.catch((error) => {
reject(error); // reject immediately, don't wait
});
});
});
}The index from forEach preserves the original order in the results array, while counter tracks how many promises have completed. These are two different jobs — confusing them is the most common bug in this exercise.
Using async/await with a for loop creates sequential execution. Each await pauses the entire loop. The fix is to use .then() inside forEach so all promises run concurrently.