Kata: Promise.all
Adopt each item, write by index, count down to zero, settle an empty input at once, and let the first rejection reject.
Predict first
Before you read: predict
EXH-2780. ExamHall's exam-start page asks for the paper, the attempt and the server time at once, and needs them by position. Its helper was written in a 15-minute kata:
On the virtual clock the server time answers at t=40, the attempt at t=90 and the paper at t=120, and none fails.
When is render called, and with what?
function loadAll(requests) { return new Promise((resolve, reject) => { const results = []; if (requests.length === 0) return resolve(results); requests.forEach((request, i) => { Promise.resolve(request).then((value) => { results[i] = value; if (results.length === requests.length) resolve(results); }, reject); }); });} loadAll([getPaper(), getAttempt(), getServerTime()]) .then(([paper, attempt, time]) => render(paper, attempt, time));The 30-second version
promiseAll reads its input once, passes every item through Promise.resolve, and writes each value at that item's index. A counter of replies still due, not results.length, says when all have fulfilled: a write at index i already makes the length at least i + 1. An empty input resolves with [] at once. Each item's rejection handler is the outer reject.
Adopt each item, write by index, count down to zero, settle an empty input at once, and let the first rejection reject.
Step through the code
Trace of the opening question
Play options
Ready: press Play or step with Next frame
Row 1 of 3 · t 40
Starting row · computed by comparing each row with the one before
- t: 40
- Reply: time, index 2
- results after the write:
[ <2 empty>, time ] - results.length: 3
- render: called: no paper, no attempt, the time
Rule · step 4
Count down. A remaining counter reaching 0 means every item fulfilled; a write at index i makes results.length at least i + 1.
- t
- 40
- Reply
- time, index 2
- results after the write
[ <2 empty>, time ]- results.length
- 3
- render
- called: no paper, no attempt, the time
- Step
- 4
| t | Reply | results after the write | results.length | render | Step |
|---|---|---|---|---|---|
| 40 | time, index 2 | [ <2 empty>, time ] | 3 | called: no paper, no attempt, the time | 4 |
| 90 (changed) | attempt, index 1 (changed) | [ <1 empty>, attempt, time ] (changed) | 3 | already called (changed) | 4 |
| 120 (changed) | paper, index 0 (changed) | [ paper, attempt, time ] (changed) | 3 | already called | 3, 4 |
Space plays or pauses · ←/→ one row · Home/End first/last
Rows come from the pack's own trace table.
The probe code (rows are not linked to code lines)
function loadAll(requests) { return new Promise((resolve, reject) => { const results = []; if (requests.length === 0) return resolve(results); requests.forEach((request, i) => { Promise.resolve(request).then((value) => { results[i] = value; if (results.length === requests.length) resolve(results); }, reject); }); });} loadAll([getPaper(), getAttempt(), getServerTime()]) .then(([paper, attempt, time]) => render(paper, attempt, time));How it works
Step 1 of 8
See it run
Not in this run
In the kitchen
- the order slip lists numbered compartments, and each one's chocolate comes from a different kitchen → the input, read once into a list (step 1)
Analogy: A chocolatier filling a numbered presentation box
| At the chocolatier | In the code |
|---|---|
| the order slip lists numbered compartments, and each one's chocolate comes from a different kitchen | the input, read once into a list (step 1) |
| chocolates already on the shelf go through the same intake slip as those still being made | Promise.resolve adopts plain values (step 2) |
| each chocolate goes into its own numbered compartment, never the next free one | results[i], not push (step 3) |
| the packer keeps a tally of empty compartments; a chocolate in the last one does not mean the box is full | a counter, not length (step 4) |
| an order for a box with no compartments ships at once | empty input (step 5) |
| one kitchen reports a ruined batch, and the box is cancelled at once | the first rejection (step 6) |
In real terms: the result array is filled by position, and a separate count says when it is complete.
Breaks down when: a cancelled box stops the kitchens. A rejection stops nothing; the other requests run on (the prerequisite's point).
Contrasting case: a tasting tray filled left to right as chocolates arrive: its order is arrival order, which is what push gives.
Examples, run for real
4.1 ExamHall MR !3080: the counter, on the opening question's replies
MR !3080 fixed EXH-2780 with the reference version (kata/promise-all.mjs): a remaining counter, index writes, an early resolve for an empty input and reject on every item. The same page, the same replies, then a run where the attempt fails:
4.1 replies time t=40, attempt t=90, paper t=120: render at t=120, with the paper, the attempt and the time
4.1 replies time t=40, attempt fails t=90 (503), paper t=120: error screen at t=90, for the attempt's 503; render never called
The page renders once every reply is in, each value in its slot (steps 3–4), and one failure shows the error screen at that moment (step 6).
4.2 Proof domain, chat: the test list, run
A chat client's teammate asks what "done" means for the kata. The answer is the six-case test file, run against the reference and against the built-in:
4.2 node --test kata/promise-all.test.mjs (reference): 6 tests · pass 6 · fail 0
4.2 KATA_IMPL=native node --test kata/promise-all.test.mjs (built-in Promise.all): 6 tests · pass 6 · fail 0
Each test is one line of step 7, and the built-in passing all six shows the tests themselves are right.
Try it
Answer first, then check. Nothing is saved.
Question 1 of 7
A chat client opens a conversation with this helper:
The members reply arrives at t=50, the messages reply at t=20 and the pinned reply at t=35. Put the replies in the order the resolved array holds them, index 0 first.
Order these chips: members reply messages reply pinned reply
function loadAll(requests) { return new Promise((resolve, reject) => { const results = []; let remaining = requests.length; if (remaining === 0) return resolve(results); requests.forEach((request) => { Promise.resolve(request).then((value) => { results.push(value); if (--remaining === 0) resolve(results); }, reject); }); });} const [members, messages, pinned] = await loadAll([getMembers(), getMessages(), getPinned()]);show(members, messages, pinned);Your order
- 1empty
- 2empty
- 3empty
Tap the lines in the order you expect.
Question 2 of 7
A chat client's "Mark as read" button runs this:
With three conversations selected, each marked at t=10, 20 and 30, the toast reads "3 marked read" at t=30. With none selected, no toast ever appears.
Which one change also shows "0 marked read" for an empty selection, and leaves the three-conversation toast as it is?
function promiseAll(iterable) { return new Promise((resolve, reject) => { const items = [...iterable]; const results = new Array(items.length); let remaining = items.length; items.forEach((item, i) => { Promise.resolve(item).then((value) => { results[i] = value; if (--remaining === 0) resolve(results); }, reject); }); });} function onMarkRead(selected) { return promiseAll(selected.map(markConversationRead)) .then((done) => toast(`${done.length} marked read`));}Question 3 of 7
ExamHall's exam-start page, with a helper a candidate wrote in the kata:
The server time answers at t=10, the attempt fails with a 503 at t=25, and the paper answers at t=60.
What does the candidate see?
function loadAll(requests) { return new Promise((resolve) => { const results = new Array(requests.length); let remaining = requests.length; if (remaining === 0) return resolve(results); requests.forEach((request, i) => { Promise.resolve(request).then((value) => { results[i] = value; if (--remaining === 0) resolve(results); }); }); });} loadAll([getPaper(), getAttempt(), getServerTime()]).then( ([paper, attempt, time]) => render(paper, attempt, time), (err) => showErrorScreen(err),);Question 4 of 7
A chat client loads a profile and the room list, using the cached profile when there is one:
The profile is in the cache, and the rooms answer at t=30.
What happens?
function loadAll(requests) { return new Promise((resolve, reject) => { const results = new Array(requests.length); let remaining = requests.length; if (remaining === 0) return resolve(results); requests.forEach((request, i) => { request.then((value) => { results[i] = value; if (--remaining === 0) resolve(results); }, reject); }); });} const cached = profileCache.get(userId); // the profile object when cached, otherwise undefinedloadAll([cached ?? fetchProfile(userId), fetchRooms(userId)]) .then(([profile, rooms]) => render(profile, rooms), (err) => showError(err));Question 5 of 7
15 minutes. In a blank file, write promiseAll(iterable) without calling Promise.all, allSettled, race or any, as you would for a chat client loading members, messages and pins together. Say your test list out loud before the code.
Which six tests do you name before you start, and what code passes them?
Question 6 of 7
A streaming service's teammate wrote this for loading a title page:
Three of their tests pass: three promises answering at t=10, 20 and 30 in list order give ["a", "b", "c"] at t=30; one rejected promise rejects with its reason; an empty array gives []. Each extra test below expects ["a", "b", "c"] once its last reply is in.
Which one fails on this version?
function loadAll(iterable) { return new Promise((resolve, reject) => { const items = [...iterable]; const results = new Array(items.length); let remaining = items.length; if (remaining === 0) return resolve(results); items.forEach((item, i) => { if (!(item instanceof Promise)) { results[i] = item; return; } item.then((value) => { results[i] = value; if (--remaining === 0) resolve(results); }, reject); }); });}Question 7 of 7
A payments ledger settles each night's card charges with this:
Tonight's three cards are each charged, but nothing is ever posted. With tonight.map(chargeCard), an array, three receipts are posted at t=30.
Which one change posts the three receipts through the generator, and keeps the kata's six tests passing?
function promiseAll(iterable) { return new Promise((resolve, reject) => { let remaining = [...iterable].length; const results = new Array(remaining); if (remaining === 0) return resolve(results); let i = 0; for (const item of iterable) { const index = i++; Promise.resolve(item).then((value) => { results[index] = value; if (--remaining === 0) resolve(results); }, reject); } });} function* pendingCharges(cards) { for (const card of cards) yield chargeCard(card);}promiseAll(pendingCharges(tonight)).then((receipts) => postToLedger(receipts));Go deeper
Pro tips
- Prove the test file on the built-in before your own code:
KATA_IMPL=native node --test kata/promise-all.test.mjs. When: writing the test list in the kata (step 7). A test the built-in fails is a wrong test, and finding that out with your own version in place costs the time box. Verify (RAN,tips.mjs):tip 1 KATA_IMPL=native node --test kata/promise-all.test.mjs: 6 tests · pass 6 · fail 0 tip 1 the same file with the empty-input test changed to expect a rejection, on the built-in: 6 tests · pass 5 · fail 1 (resolves an empty input with [])
Source
MDN and the interview handbook change between releases, so their claims are labelled as of 2026-10-04.
- MDN, Promise.all(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all (
mdn-promise-all.txt), as of 2026-10-04. "The fulfillment value is an array of fulfillment values, in the order of the promises passed, regardless of completion order." "Already fulfilled, if the iterable passed is empty." "These values are awaited, so other thenables are also resolved, while non-thenables are returned as-is." Supports steps 2, 3 and 5. - ECMA-262, Promise.all ( iterable ) and PerformPromiseAll, 27.5.4.1: https://tc39.es/ecma262/#sec-promise.all (
ecma262-promise-all.txt, the Promise.all clauses of the single-page spec), as of 2026-10-04. The algorithm callspromiseResolveon each value, writes it atthisIndex, and countsremainingElementsCountdown; it starts that count at 1 and drops it after the loop, which settles an empty input the same way step 5 does. Supports steps 1–6. - Front End Interview Handbook, JavaScript utility functions: https://www.frontendinterviewhandbook.com/coding/javascript-utility-function (
feih-utility-functions.txt), as of 2026-10-04 (page last updated 13 August 2026). It lists "Implement Promise/Promise-related APIs: Promise, Promise.all, Promise.any", advises preparing "by implementing them yourself and writing test cases for them", and says "Candidates are expected to take just around 10-15 minutes for a basic question." Supports steps 7–8.
Related lessons
Checked against the tools and docs as of 2026-10-04.