ESC

Type to search the knowledge base.

Promise Combinators Practice

Drill Promise.all, allSettled, race, and any with real patterns — timeouts, fail-soft loads, and first-success fallbacks.

intermediate3 min read
  • 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 guides