Promise Combinators Practice
Drill Promise.all, allSettled, race, and any with real patterns — timeouts, fail-soft loads, and first-success fallbacks.
- javascript
- promise-combinators
Knowing the table of combinators isn’t enough. Interviews and production need patterns: timeout a fetch, load widgets fail-soft, race a cache vs network. Here’s the practice sheet.
Quick map
| API | Fulfills when | Rejects when |
|---|---|---|
all |
all fulfill | first reject |
allSettled |
all settle | never (always fulfills with results) |
race |
first settle | that first is reject |
any |
first fulfill | all reject (AggregateError) |
const urls = ['/a', '/b', '/c'];
const settled = await Promise.allSettled(urls.map((u) => fetch(u)));
const ok = settled
.filter((r) => r.status === 'fulfilled')
.map((r) => r.value);
Pattern: timeout with race
function withTimeout(promise, ms, msg = 'Timeout') {
let id;
const timeout = new Promise((_, reject) => {
id = setTimeout(() => reject(new Error(msg)), ms);
});
return Promise.race([promise, timeout]).finally(() => clearTimeout(id));
}
const res = await withTimeout(fetch('/api'), 8000);
Note: racing does not cancel fetch unless you also AbortController.abort() in the timeout path.
async function fetchWithTimeout(url, ms) {
const ac = new AbortController();
const id = setTimeout(() => ac.abort(), ms);
try {
return await fetch(url, { signal: ac.signal });
} finally {
clearTimeout(id);
}
}
Pattern: fail-soft dashboard
const [user, feed, ads] = await Promise.allSettled([
loadUser(),
loadFeed(),
loadAds(),
]);
render({
user: user.status === 'fulfilled' ? user.value : guest,
feed: feed.status === 'fulfilled' ? feed.value : [],
ads: ads.status === 'fulfilled' ? ads.value : null,
});
all would blank the whole page if ads fail — usually wrong product behavior.
Pattern: first healthy mirror (any)
const res = await Promise.any([
fetch('https://cdn-a.example/x'),
fetch('https://cdn-b.example/x'),
]);
// first 200-ish fulfillment — still check res.ok yourself
any cares about promise fulfillment, not HTTP status. Wrap:
async function fetchOk(url) {
const r = await fetch(url);
if (!r.ok) throw new Error(String(r.status));
return r;
}
Pattern: cache vs network (race carefully)
// First to settle wins — a fast network error can beat a slow cache!
// Prefer explicit strategy:
async function cacheFirst(key, networkFn) {
const cached = await caches.match(key);
if (cached) return cached;
return networkFn();
}
Blind Promise.race([cache, network]) is rarely the policy you want without error handling rules.
Concurrency limit (not a built-in)
async function mapPool(items, limit, worker) {
const ret = new Array(items.length);
let i = 0;
async function run() {
while (i < items.length) {
const idx = i++;
ret[idx] = await worker(items[idx], idx);
}
}
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, run));
return ret;
}
Combinators start everything; pools control pressure on APIs.
Interview answer (out loud)
“I pick all for required parallel success, allSettled for partial UI, race for first settlement including failure, any for first success. Timeouts need race or AbortController. Combinators don’t cancel other work — I abort explicitly when needed.”
Further reading
Related
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.