Promise.all Polyfill
Implement your own Promise.all polyfill to understand how concurrent Promise handling works under the hood.
JavaScript · Promises · Async/Await — Pratik Rai ·
Implement the three Promise utility variants: allSettled (never rejects), race (first to settle wins), and any (first to resolve wins).
Promise.allSettled, Promise.race and Promise.any are the three combinators people reach for after outgrowing Promise.all. Each exists because all makes one specific trade-off — fail fast, wait for everything — that is wrong for a large class of problems. Implementing all three together is the fastest way to internalise which one you actually need.
| Combinator | Settles when | Rejects when | Result shape |
|---|---|---|---|
all | every promise fulfils | any rejects, immediately | array of values |
allSettled | every promise settles | never | array of status objects |
race | the first promise settles | if that first one rejected | single value |
any | the first promise fulfils | all reject | single value |
The two easy-to-confuse pairs: race versus any (race cares about the first to settle, any about the first to succeed), and all versus allSettled (fail-fast versus report-everything).
allSettled — never rejectsUse it when partial failure is acceptable and you want to know exactly what happened to each item. Loading six dashboard widgets where two might fail is the canonical case: all would blank the whole dashboard.
JSfile.javascript1function allSettled(iterable) { 2 return new Promise((resolve) => { 3 const items = Array.from(iterable); 4 const results = new Array(items.length); 5 let remaining = items.length; 6 7 if (remaining === 0) return resolve([]); 8 9 items.forEach((item, index) => { 10 Promise.resolve(item).then( 11 (value) => { 12 results[index] = { status: 'fulfilled', value }; 13 if (--remaining === 0) resolve(results); 14 }, 15 (reason) => { 16 results[index] = { status: 'rejected', reason }; 17 if (--remaining === 0) resolve(results); 18 } 19 ); 20 }); 21 }); 22}
The outer promise takes no reject parameter at all — that is the entire design. Both branches record and decrement identically.
Reading the output is the part people get wrong. Every entry needs its status checked before touching .value:
JSfile.javascript1const settled = await Promise.allSettled(tasks); 2const succeeded = settled.filter((r) => r.status === 'fulfilled').map((r) => r.value); 3const failed = settled.filter((r) => r.status === 'rejected').map((r) => r.reason);
race — first to settle, win or loseJSfile.javascript1function race(iterable) { 2 return new Promise((resolve, reject) => { 3 for (const item of iterable) { 4 Promise.resolve(item).then(resolve, reject); 5 } 6 }); 7}
Strikingly short, because promises are already single-assignment: once resolve or reject has been called, every later call is ignored. No bookkeeping required.
The classic use is a timeout:
JSfile.javascript1const withTimeout = (promise, ms) => 2 Promise.race([ 3 promise, 4 new Promise((_, rej) => setTimeout(() => rej(new Error('Timeout')), ms)), 5 ]);
Two traps here. An empty iterable means the promise never settles — race([]) hangs forever, silently. And the losers keep running; the timeout above rejects your promise but the underlying request continues to completion. Pair it with AbortController when the work has a cost.
any — first to succeedany ignores rejections until every single promise has failed, then rejects with an AggregateError carrying all the reasons.
JSfile.javascript1function any(iterable) { 2 return new Promise((resolve, reject) => { 3 const items = Array.from(iterable); 4 const errors = new Array(items.length); 5 let remaining = items.length; 6 7 if (remaining === 0) { 8 return reject(new AggregateError([], 'All promises were rejected')); 9 } 10 11 items.forEach((item, index) => { 12 Promise.resolve(item).then(resolve, (error) => { 13 errors[index] = error; 14 if (--remaining === 0) { 15 reject(new AggregateError(errors, 'All promises were rejected')); 16 } 17 }); 18 }); 19 }); 20}
resolve is passed directly as the fulfilment handler — the first success wins and subsequent calls are no-ops. Errors are collected by index so the AggregateError.errors array lines up with the input.
Use it for redundancy: three CDN mirrors, several geolocation strategies, a cache and a network fetch racing where either is acceptable.
A few heuristics that resolve most cases:
Promise.all in try/catch and carrying on anyway? You wanted allSettled.race.any.all is right.Empty iterables differ per combinator. all([]) and allSettled([]) resolve immediately. any([]) rejects with an empty AggregateError. race([]) hangs forever. That last one is a real bug source when the array is built dynamically.
None of them cancel anything. All four start every promise immediately and leave losers running. Combinators control what you wait for, never what executes.
AggregateError needs a modern target. It landed with Promise.any in ES2021; older browsers need a polyfill for the error type itself, not just the method.
allSettled never rejects — check status on every entry.race settles on the first settlement; any on the first success.race([]) never settles; any([]) rejects immediately.race needs no bookkeeping.AbortController when losers are expensive.Goal: Implement all three variants. Focus on the structural difference between them — not just the code.
Continue learning with these related challenges
Implement your own Promise.all polyfill to understand how concurrent Promise handling works under the hood.
JavaScript · Promises · Async/Await — Pratik Rai ·
Implement a retry function that handles transient failures by retrying async operations with optional delays.
JavaScript · Promises · Async/Await — Pratik Rai ·
Implement a Function.prototype.call polyfill from scratch. Master JavaScript this binding, execution context, primitive boxing, and safe property assignment techniques.
JavaScript · Functions · this — Pratik Rai ·
Implement your own Promise.all polyfill to understand how concurrent Promise handling works under the hood.
JavaScript · Promises · Async/Await
Pratik Rai ·
Implement a retry function that handles transient failures by retrying async operations with optional delays.
JavaScript · Promises · Async/Await
Pratik Rai ·
Implement a Function.prototype.call polyfill from scratch. Master JavaScript this binding, execution context, primitive boxing, and safe property assignment techniques.
JavaScript · Functions · this
Pratik Rai ·