JS Sketchbooksee JavaScript think ✏️
← back to phase 6

6.8 — Parallel & racing

Lesson 6.6 left a warning: await a(); await b(); runs one after the other — two independent 80ms waits become 160ms. In a test suite fetching five fixtures per test, that's the difference between a 2-minute run and a 10-minute one. Independent waits should overlap.

JavaScript ships four combinators for exactly this — Promise.all, allSettled, race, any — each answering a different question about a set of promises: all of them? every verdict? the first to settle? the first to succeed?

Plus one ordering surprise that trips even seniors: results come back in input order, not finish order.

watch it happen
function delay(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}

async function main() {
  const salad = delay(60, "salad");
  const soup = delay(20, "soup");

  const first = await Promise.race([salad, soup]);
  console.log(first);

  const both = await Promise.all([salad, soup]);
  console.log(both);
}

main();

The bill, first: await delay(60) then await delay(20) = 80ms total, because the second timer doesn’t even START until the first await finishes.

the kitchen lanes (time →)salad 60mssoup 20msawait a; await b; → 60 + 20 = 80mssequential awaits ADD UP — the second wait doesn’t even start until thefirst ends(console: nothing yet)
under the hood

The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”

None of these run promises "in parallel" themselves — the overlap happened when you started the work (the executor runs at construction, 6.4). The combinators just observe a set of receipts and settle their own single receipt by a rule. One thread throughout (6.1's law): the waiting overlaps in the environment, never the JS.

💼 On the job — Playwright code awaits sequentially when steps depend (click → then assert). Promise.all is for genuinely simultaneous things. The classic: await Promise.all([page.waitForNavigation(), page.click("a")]) — start listening BEFORE the click that triggers it. Starting-then-awaiting is a professional reflex; today it enters your hands.

And the input-order guarantee is what makes all composable with destructuring: const [user, cart] = await Promise.all([getUser(), getCart()]) — 4.11's array pattern, matched by position, safe because position is promised.

your turn

⌨️ race them, then feed everyone

Two dishes, different cooking times. Prove you can ask both of the phase’s parallel questions: who’s FIRST — and, when everything’s ready, in what ORDER do the results arrive?

requirements:

  • A helper delay(ms, value) — a promise resolving with value after ms.
  • Start BOTH before any await: slow = 60ms → "salad", fast = 20ms → "soup". Await the RACE of [slow, fast] and print the winner.
  • Then await ALL of [slow, fast] and print the array — and notice it comes back in INPUT order, salad first, even though soup finished first.

when you press RUN, the console must show exactly:

soup
["salad","soup"]

✏️ Quick check 1

a takes 300ms, b takes 500ms, both started first. How many ms until await Promise.all([a, b]) finishes? Type the number.

✏️ Quick check 2

p1 (80ms → "A") and p2 (30ms → "B"). Type EXACTLY what await Promise.all([p1, p2]) fulfills with:

✏️ Quick check 3

Five promises; two reject. Which combinator fulfills anyway, with a verdict for each? Type its name.

teach it back

🗣️ Now teach it back

Explain to a friend: why do two independent awaits waste time, how do you overlap them, what do all / allSettled / race / any each answer — and what order do all’s results arrive in?

Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.

a few sentences, minimum — you’ve got this
to remember
Independent waits: START both first, then await — overlap makes the total max(), not sum(). Sequential awaits are for dependent steps only.
all = everything (one rejection kills it) · allSettled = every verdict, never rejects · race = first settle either way · any = first success.
all’s results keep INPUT order — all[0] is input[0], finish order forgotten — so destructuring them is safe.