Skip to content

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?

Code for this question
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),);
Pick one

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
Trace of the opening question
tReplyWhat results[i] holdsresults[i].statusPanelStep
20gradethe grade objectundefinedwaits for the rest3
45 (changed)feedback's 503 (changed)the Error the catch returned, as a value (changed)undefinedwaits for the rest3
70 (changed)certificate (changed)the certificate object (changed)undefinedall 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)
The probe code
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

Build on 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

  1. 1empty
  2. 2empty
  3. 3empty
  4. 4empty
  5. 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?

Code for this question
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`);
Pick one

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?

Code for this question
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);
Pick one

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?

Code for this question
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);  });});
Pick one

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?

Code for this question
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)),);
Pick one

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?

Code for this question
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(', ')})`);
Pick one

Go deeper

Pro tips
  1. Build it from Promise.all in two lines, then spend the time on Promise.any. When: the kata's first minutes (steps 1–2). The whole stand-in is one Promise.all over one map; 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.

Checked against the tools and docs as of 2026-10-12.