Skip to Content
JavaScript Advanced: Code That Holds Up in Production
Advanced
0 likes
Courses
JavaScript Advanced: Code That Holds Up in Production

Running Promises in Parallel: all, allSettled, race

Start independent work together, choose between Promise.all, allSettled, race and any, and limit how many requests run at once. After this, a page with ten API calls stops waiting for them one by one.

Course content

Sign in to save your progress.

Course Home

Skip ahead: If you already know the difference between Promise.all and Promise.allSettled, skip to "Limit how many run at once".

await in a loop runs requests one after another. If each takes 300 ms and you have ten, the user waits three seconds for work that could take 300 ms. When the requests don't depend on each other, start them all, then wait for all of them. JavaScript has four functions for that, and picking the wrong one either throws away good results or hides failures.

One by one vs together

const wait = (ms, value) => new Promise(resolve => setTimeout(() => resolve(value), ms));

async function oneByOne() {
  const start = Date.now();
  for (const id of [1, 2, 3]) await wait(100, id);
  return Date.now() - start >= 300 ? "300+ ms" : "under 300 ms";
}

async function together() {
  const start = Date.now();
  await Promise.all([1, 2, 3].map(id => wait(100, id)));
  return Date.now() - start < 200 ? "about 100 ms" : "200+ ms";
}

console.log("one by one:", await oneByOne());
console.log("together:", await together());
one by one: 300+ ms
together: about 100 ms

(This example uses await at the top level, so save it as .mjs or run it in a project with "type": "module".)

[1, 2, 3].map(id => wait(100, id)) starts all three right away. Promise.all only waits. The work was already running.

⚠ Warning: Only do this when the calls are independent. If step 2 needs the result of step 1, they must run in order.

The four functions

const ok = (ms, value) => new Promise(resolve => setTimeout(() => resolve(value), ms));
const fail = (ms, message) => new Promise((_, reject) => setTimeout(() => reject(new Error(message)), ms));

try {
  console.log("all:", await Promise.all([ok(50, "A"), fail(20, "B failed"), ok(10, "C")]));
} catch (error) {
  console.log("all rejected:", error.message);
}

const settled = await Promise.allSettled([ok(50, "A"), fail(20, "B failed"), ok(10, "C")]);
console.log("allSettled:", settled.map(r => r.status === "fulfilled" ? r.value : r.reason.message));

console.log("race:", await Promise.race([ok(50, "slow"), ok(10, "fast")]));
console.log("any:", await Promise.any([fail(5, "x"), ok(30, "first success")]));
all rejected: B failed
allSettled: [ 'A', 'B failed', 'C' ]
race: fast
any: first success
  • Promise.all: all results, in the same order you passed them. Rejects as soon as one rejects. Use it when you need everything, and one failure means the whole thing failed (a page that needs the user, their settings and their cart).
  • Promise.allSettled: never rejects. Gives { status, value } or { status, reason } for each. Use it for batches where each item can fail on its own (sending 50 notifications).
  • Promise.race: settles with the first one that settles, success or failure. Mostly used for timeouts, though AbortSignal.timeout (next lesson) is better for fetch.
  • Promise.any: the first one that succeeds. Rejects with an AggregateError only if all fail. Use it for "try three mirrors, take whichever answers".

💡 Tip: When Promise.all rejects, the other promises keep running. It just stops waiting for them. If they have side effects (writes, emails), those still happen. If you need them to stop, you have to cancel them yourself.

Limit how many run at once

Starting everything at once is fine for 5 calls. For 5,000, you'll hit rate limits, run out of sockets, or take down someone else's server. Run them in a pool with a fixed limit:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

async function mapWithLimit(items, limit, worker) {
  const results = new Array(items.length);
  let next = 0;
  let running = 0;
  let maxRunning = 0;
  async function lane() {
    while (next < items.length) {
      const index = next++;
      running++;
      maxRunning = Math.max(maxRunning, running);
      results[index] = await worker(items[index]);
      running--;
    }
  }
  await Promise.all(Array.from({ length: limit }, lane));
  return { results, maxRunning };
}

const { results, maxRunning } = await mapWithLimit([1, 2, 3, 4, 5, 6, 7], 3, async id => {
  await wait(20);
  return id * 10;
});
console.log(results);
console.log("max at once:", maxRunning);
[
  10, 20, 30, 40,
  50, 60, 70
]
max at once: 3

Three "lanes" each take the next item when they finish one. Results stay in the original order. If you'd rather not write this yourself, the p-limit package does the same thing.

Common mistakes

  • await inside a for loop for calls that don't depend on each other.
  • Promise.all for a batch where one bad item shouldn't cancel the report for the others.
  • Forgetting that .map(async ...) returns an array of promises, not results. You still need Promise.all.
  • items.forEach(async item => await save(item)). forEach doesn't wait, so the code after it runs before anything is saved.
  • No limit on 1,000+ parallel requests.

What happens if you skip this

Your pages are slow because every call waits for the previous one. Then you switch to Promise.all everywhere, and one failed request blanks out a dashboard that had nine good results to show.

You need the user's profile and their last 20 orders to render a page, and a "recently viewed" list that's nice to have. Which function would you use for each, and why not one for all three?

About

Start independent work together, choose between Promise.all, allSettled, race and any, and limit how many requests run at once. After this, a page with ten API calls stops waiting for them one by one.

Duration
5 min
Last update
08/20/2026
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.