Kata: Promise.allSettled
Two handlers in one then on every input, an entry with a status from each, and Promise.all over the lot.
Predict first
Before you read: predict
EXH-3500. ExamHall's results page loads the grade, the feedback and the certificate independently with Promise.allSettled. One test centre's kiosk browser lacks it, so a teammate wrote a stand-in in a 15-minute kata:
On the virtual clock the grade answers at t=20, the feedback fails with a 503 at t=45, and the certificate answers at t=70.
What does the kiosk show?
function allSettled(requests) { return Promise.all(requests.map((request) => request.catch((error) => error)));} allSettled([getGrade(), getFeedback(), getCertificate()]).then( (results) => { ['grade', 'feedback', 'certificate'].forEach((panel, i) => { if (results[i].status === 'fulfilled') show(panel, results[i].value); else markFailed(panel); }); }, (error) => showErrorScreen(error),);The 30-second version
Map every input to a promise that always fulfils, then hand the list to Promise.all. Adopt the item, then give one then two handlers: a value becomes { status: 'fulfilled', value }, a reason becomes { status: 'rejected', reason }. Promise.all keeps input order, waits for the last input and never sees a rejection, so the result never rejects.
Two handlers in one then on every input, an entry with a status from each, and Promise.all over the lot.
Step through the code
Trace of the opening question
Play options
Ready: press Play or step with Next frame
Row 1 of 3 · t 20
Starting row · computed by comparing each row with the one before
- t: 20
- Reply: grade
- What results[i] holds: the grade object
- results[i].status:
undefined - Panel: waits for the rest
Rule · step 3
Keep the status. Only the entry's status tells a rejection from an Error an input fulfilled with. .catch((error) => error) keeps the error, drops the tag.
- t
- 20
- Reply
- grade
- What results[i] holds
- the grade object
- results[i].status
undefined- Panel
- waits for the rest
- Step
- 3
| t | Reply | What results[i] holds | results[i].status | Panel | Step |
|---|---|---|---|---|---|
| 20 | grade | the grade object | undefined | waits for the rest | 3 |
| 45 (changed) | feedback's 503 (changed) | the Error the catch returned, as a value (changed) | undefined | waits for the rest | 3 |
| 70 (changed) | certificate (changed) | the certificate object (changed) | undefined | all three marked failed (changed) | 1, 3 |
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 allSettled(requests) { return Promise.all(requests.map((request) => request.catch((error) => error)));} allSettled([getGrade(), getFeedback(), getCertificate()]).then( (results) => { ['grade', 'feedback', 'certificate'].forEach((panel, i) => { if (results[i].status === 'fulfilled') show(panel, results[i].value); else markFailed(panel); }); }, (error) => showErrorScreen(error),);How it works
Step 1 of 8
promiseAll. If every mapped input fulfils, Promise.all keeps their order, settles with the last one and fulfils an empty input with [].See it run
In the kitchen
- the keeper's sheet has one numbered line per bird flown, filled in whatever order the birds come back → the result array, by input index (step 1)
Analogy: A falconry centre's flying-day sheet
| At the falconry centre | In the code |
|---|---|
| the keeper's sheet has one numbered line per bird flown, filled in whatever order the birds come back | the result array, by input index (step 1) |
| each line gets a tick in one box: "back on the glove" with its weight, or "lost" with where it was last seen | a { status, value } or { status, reason } entry (steps 2–3) |
| a hawk back on the glove with the remark "injured talon" is still back; only the box says so | a fulfilled Error value stays fulfilled (step 3) |
| a volunteer copies the remarks but not the boxes, and "lost: injured, last seen by the wood" now reads like a returned bird's remark | .catch((error) => error) (step 3) |
| one lost bird never calls off the day; the sheet is filed when every line has a tick | never rejects; settles with the last input (steps 5–6) |
In real terms: each input reports through its own tagged entry, and the list is complete when every input has one.
Breaks down when: a keeper files the sheet at dusk with a line still blank. Here an input that never settles keeps the result pending for good.
Contrasting case: a display called off the moment one bird is lost is Promise.all on the raw inputs.
Examples, run for real
4.1 ExamHall MR !3800: the reference, on the opening question's replies
MR !3800 fixed EXH-3500 by replacing the stand-in with the reference (kata/promise-all-settled.mjs): two handlers in one then on each request, under Promise.all. The same page and replies, then the built-in on the same replies:
4.1 reference (MR !3800), replies grade t=20, feedback fails 503 t=45, certificate t=70: at t=70, the grade and the certificate, with the feedback marked failed
4.1 built-in Promise.allSettled, same replies: at t=70, the grade and the certificate, with the feedback marked failed
The page now marks only the feedback failed, at the last reply (steps 2, 3 and 6), as the built-in does.
4.2 Proof domain, payments ledger: the test lists, run
A payments ledger's reconciliation job must run on an embedded runtime without Promise.allSettled. The reviewer asks what "done" means: the two test files, run against the references and the built-ins, then against the row's misconception:
4.2 node --test kata/promise-all-settled.test.mjs kata/promise-any.test.mjs (references): 11 tests · pass 11 · fail 0
4.2 KATA_IMPL=native, the same two files (built-in allSettled and any): 11 tests · pass 11 · fail 0
4.2 the row's misconception, .catch((error) => error) on each input, against the kata's six tests: 3 pass, fails "gives one { status, value } or { status, reason } entry per input, in input order", "keeps a fulfilled Error value fulfilled, apart from a rejection", "takes plain values and thenables as items"
The built-ins passing every test shows the tests are right; the catch version fails three tests, each for the missing status (step 3).
Try it
Answer first, then check. Nothing is saved.
Question 1 of 7
A payments ledger's reconciliation job runs on an embedded runtime with no Promise.allSettled, and reads each entry's status. Rebuild the stand-in from its lines. The first line is function promiseAllSettled(iterable) { and the last is }.
The first line is function promiseAllSettled(iterable) { and the last is }. Put them in order.
Your order
- 1empty
- 2empty
- 3empty
- 4empty
- 5empty
Tap the lines in the order you expect.
Question 2 of 7
A ticketing site's nightly job releases expired seat holds at three venues with the team's stand-in:
The arena answers at t=20, the theatre's release rejects with a 504 error at t=35, and the club answers at t=50.
What does the job do?
function promiseAllSettled(items) { return Promise.all(items.map((item) => Promise.resolve(item).then( (value) => ({ status: 'fulfilled', value }), (reason) => { status: 'rejected', reason }, )));} const results = await promiseAllSettled(venues.map(releaseHolds));const failed = results.filter((r) => r.status === 'rejected');log(`${results.length - failed.length} released, ${failed.length} failed`);Question 3 of 7
A payments ledger charges three cards through a processor SDK that fulfils with a DeclineError when a bank declines (the bank answered) and rejects with a TimeoutError when the call times out. Only a timed-out charge may be retried. The retry job:
c1 is charged at t=20, c2's bank declines at t=30, and c3 times out at t=50.
Which cards does the job queue for a retry?
const settle = (charges) => Promise.all(charges.map((charge) => charge.catch((error) => error))); const results = await settle([chargeCard('c1'), chargeCard('c2'), chargeCard('c3')]);const retry = results.flatMap((result, i) => (result instanceof Error ? [`c${i + 1}`] : []));queueRetry(retry);Question 4 of 7
A streaming service's home page loads three rows with this stand-in:
Continue watching answers at t=30, trending rejects with a 500 error at t=15, and new releases answers at t=60.
What does the home page show?
function promiseAllSettled(items) { return Promise.all(items.map((item) => Promise.resolve(item).then((value) => ({ status: 'fulfilled', value })))) .catch((reason) => [{ status: 'rejected', reason }]);} promiseAllSettled([getContinueWatching(), getTrending(), getNewReleases()]).then((results) => { ['continue watching', 'trending', 'new releases'].forEach((row, i) => { if (!results[i]) return; if (results[i].status === 'fulfilled') showRow(row, results[i].value); else showRetry(row); });});Question 5 of 7
15 minutes. In a blank file, write promiseAllSettled(iterable) on top of Promise.all, without calling allSettled, race or any, as you would for the ledger's reconciliation job. Say your test list out loud before the code. Then the interviewer's follow-up: write promiseAny(iterable) without any, and say what it rejects with.
Which tests do you name before you start, and what code passes them?
Question 6 of 7
A payment router tries three processors and takes the first charge that succeeds, with a promiseAny a teammate adapted from the team's promiseAll:
Tonight every processor declines: stripe-eu at t=20, adyen at t=35 and backup at t=50, and charge() rejects with the processor's decline.
What does the router do?
function promiseAny(iterable) { return new Promise((resolve, reject) => { const items = [...iterable]; const errors = new Array(items.length); let remaining = items.length; if (remaining === 0) return reject(errors); items.forEach((item, i) => { Promise.resolve(item).then(resolve, (reason) => { errors[i] = reason; if (--remaining === 0) reject(errors); }); }); });} await promiseAny(processors.map(charge)).then( (receipt) => post(receipt), (error) => (error instanceof AggregateError ? showAllDeclined(error.errors.map((e) => e.message)) : showOutage(error)),);Question 7 of 7
A chat app exports three conversations overnight with a teammate's stand-in:
Conversation c1 is exported at t=10, c2's export rejects with an Error 'c2: 404' at t=25, and c3 is exported at t=40.
What does the export job do?
function promiseAllSettled(items) { return Promise.all(items.map((item) => Promise.resolve(item).then( (value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', value: reason }), )));} const results = await promiseAllSettled(conversations.map(exportConversation));const failed = results.filter((r) => r.status === 'rejected');log(`${failed.length} failed (${failed.map((r) => r.reason?.message ?? 'no reason').join(', ')})`);Go deeper
Pro tips
- Build it from
Promise.allin two lines, then spend the time onPromise.any. When: the kata's first minutes (steps 1–2). The whole stand-in is onePromise.allover onemap; written compactly it is two lines and passes the six tests, which leaves most of the time box for the follow-up. Verify (RAN,tips.mjs):tip 1 the two-line version (one Promise.all over one map): 2 lines · against the kata's six tests: 6 pass
Source
MDN changes between releases, so its claims are labelled as of 2026-10-12.
- MDN, Promise.allSettled(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled (
mdn-promise-allsettled.txt), as of 2026-10-12. "The fulfillment value is an array of objects, each describing the outcome of one promise in the iterable, in the order of the promises passed, regardless of completion order." "A string, either "fulfilled" or "rejected", indicating the eventual state of the promise." "Already fulfilled, if the iterable passed is empty." Supports steps 1, 3 and 6. - MDN, Promise.any(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any (
mdn-promise-any.txt), as of 2026-10-12. "It rejects when all of the input's promises reject (including when an empty iterable is passed), with an AggregateError containing an array of rejection reasons." "Already rejected, if the iterable passed is empty." Supports step 7. - MDN, Arrow function expressions (not a row source; read for step 4): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions (
mdn-arrow-functions.txt), as of 2026-10-12. "the code inside braces ({}) is parsed as a sequence of statements, where foo is a label, not a key in an object literal." "To fix this, wrap the object literal in parentheses".
Related lessons
Checked against the tools and docs as of 2026-10-12.