cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Promise combinators

Learn how JavaScript combines many promises with Promise.all, allSettled, race, any, withResolvers, Promise.try, and the proposed keyed helpers.

By the end, choosing a combinator should feel natural
  • 01
    Wait for the right amountUse all, allSettled, race, and any when you need every result, every report, the first settlement, or the first success.
  • 02
    Handle edge cases honestlyPredict empty iterables, input order, fail-fast rejection, AggregateError, and non-promise values.
  • 03
    Recognize newer toolsFeature-detect withResolvers, Promise.try, and the Stage 3 allKeyed proposal while using safe fallbacks.

Many promises, one plan

A single promise is a value for work that finishes later. Real pages rarely wait for just one thing. A dashboard may need the user, feature flags, notifications, and chart data. A file uploader may start many uploads. A CDN loader may ask three mirrors and keep the first good answer.

Promise combinators are static methods that take an iterable of promises or values and return one new promise that describes the group. This lesson builds on Promises and Microtasks in depth. The previous lessons by title, Promise chaining and Error handling with promises, explain how values and rejections travel once the group promise settles.

The short version

Use Promise.all for every success, Promise.allSettled for every report, Promise.race for the first settlement, and Promise.any for the first fulfillment.

Real-life analogyPromise.all is a group dinner

Imagine four friends ordered together. The table is served only when every dish is ready. If the kitchen drops one dish, the waiter comes back immediately with the bad news. But the soup and dessert do not magically vanish from the kitchen; they keep cooking.

In real life: Every dish reaches the table
In JavaScript: Every input fulfills, so Promise.all fulfills
In real life: The waiter keeps dish positions straight
In JavaScript: The result array follows input order
In real life: One dish is dropped
In JavaScript: The combined promise rejects with the first rejection reason
In real life: Other dishes keep cooking
In JavaScript: Fail-fast does not cancel the other promises

Where the analogy stops: A restaurant might tell the kitchen to stop. Promise.all has no built-in cancellation; timers, requests, and other work keep running unless you add your own cancellation signal.

That is the pattern for the whole lesson: each method answers a different group question. Do you need every value, every outcome, the first outcome, or the first success?

Promise.all & allSettled

INTERACTIVE

Promise.all(iterable) accepts any iterable. Each item can be a promise, a thenable, or a plain value. Plain values count as already fulfilled. If the iterable is empty, Promise.all([]) fulfills with an empty array.

The success result is an array in input order, not completion order. That matters when one promise is slow but appears first in the array. Try the kitchen race track, then step through the fail-fast replay.

Kitchen race track
Four kitchen tasksPop out in the code editor (opens in a new tab)JavaScript
const tasks = [  task("A", 800, true),  task("B", 450, true),  task("C", 1100, false),]; Promise.all(tasks);        // every task must fulfillPromise.allSettled(tasks); // report every resultPromise.race(tasks);       // first settlement winsPromise.any(tasks);        // first fulfillment wins
Timelineall
Task A
Task B
Task C
Task D

Press Run to start real promises.

  1. No settlements yet.
Try it yourself

Promise.all waits for every fulfillment, but rejects as soon as the first task rejects. Notice that the other timers still finish.

Real promises and timers run in your browser. Reset clears active timers and restores the starting tasks. The star is the combined promise.
Promise.all: fail fast, not cancel fast
Step 0 of 9Ready
Your turn: follow the blue line

Choose which task fails. Predict whether Promise.all logs an array or the first rejection reason.

Running in
  1. script
Next: line 11
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
function cook(label, ms, shouldFail) {  return new Promise((resolve, reject) => {    setTimeout(() => {      if (shouldFail) reject(label + " dropped");      else resolve(label + " ready");    }, ms);  });} const orders = [  cook("B", 10, true),  cook("C", 20, false),]; Promise.all(orders).then(  (values) => console.log(values.join(" | ")),  (reason) => console.log("failed: " + reason),);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Which task fails?
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Promise.allSettled(iterable) waits for everything and always fulfills because of the inputs. Each result is an object: either { status: "fulfilled", value } or { status: "rejected", reason }. Use it when failures are data you still need to show, such as upload reports or test results.

Real-life analogyallSettled is a teacher collecting every exam

A teacher does not stop collecting exams because one student failed. The report is useful only after every paper is in. That is allSettled: no early exit, no missing rows.

In real life: Every paper is collected
In JavaScript: Every input settles before the combined promise fulfills
In real life: Some exams pass, some fail
In JavaScript: Each result has a fulfilled or rejected status
In real life: The teacher announces the full class report
In JavaScript: You receive an array of status objects

Where the analogy stops: The teacher might grade papers later. Promise.allSettled does not grade or fix failures; it only reports each settlement.

Promise.race & Promise.any

SORT IT

Promise.race(iterable) settles as soon as the first input settles. If the fastest promise fulfills, the race fulfills. If the fastest promise rejects, the race rejects. An empty race, Promise.race([]), stays pending forever because there is no first settlement.

Promise.any(iterable) waits for the first fulfillment. Rejections are ignored until every input has rejected. If that happens, the returned promise rejects with an AggregateError, and its .errors array is in input order. Promise.any([]) rejects immediately with an AggregateError because there can never be a fulfillment.

Real-life analogyrace is a horse race; any is job applications

In a horse race, the first event decides the headline: a horse wins or a favorite falls. In job applications, a single yes is enough; you give up only when everyone says no. That is the difference between race and any.

In real life: First horse crosses the line or falls
In JavaScript: Promise.race uses the first settlement, fulfilled or rejected
In real life: One job offer is enough
In JavaScript: Promise.any fulfills with the first success
In real life: Every company says no
In JavaScript: Promise.any rejects with AggregateError

Where the analogy stops: A race usually has one winner and ends for everyone. Promise.race does not cancel the slower promises; they can still settle later.

Which combinator fits?
  • Load profile, permissions, and settings before showing a dashboard
  • Upload 8 photos and show which ones succeeded or failed
  • Use a timeout promise to stop waiting after 3 seconds
  • Try three CDNs and use the first successful response
  • Collect every exam result before announcing the class report
  • Show whichever connection attempt settles first, success or failure
  • Wait for soup, salad, and dessert before serving a table
  • Send applications and celebrate the first yes
Try it yourself
0 of 8 correct

Choose the group method that matches each real scenario.

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Edge cases worth rememberingPop out in the code editor (opens in a new tab)JavaScript
await Promise.all(["plain", Promise.resolve("promise")]);await Promise.all([]);Promise.race([]); // stays pending foreverawait Promise.any([]); // rejects with AggregateError

Promise.withResolvers

NEWER API

Sometimes the code that creates a promise is not the same code that knows when to settle it. Promise.withResolvers() returns an object with promise, resolve, and reject. It is a Baseline 2024/newer-environment feature; Node 22 includes it, but feature detection is still healthy when you support older browsers.

Real-life analogyThe counter holds the buttons

You get the token, while the counter keeps the buttons that mark it ready or sold out. withResolvers lets a careful piece of code, such as an event listener, use those buttons.

In real life: You hold an order token
In JavaScript: The promise someone can await
In real life: The counter marks it ready
In JavaScript: The resolve function
In real life: The counter marks it sold out
In JavaScript: The reject function

Where the analogy stops: Handing out resolve and reject is powerful. Keep them private unless another part of your code truly owns the event that settles the promise.

withResolvers order token
waitForClick built with withResolversPop out in the code editor (opens in a new tab)JavaScript
function waitForClick(button) {  const { promise, resolve, reject } = Promise.withResolvers();   button.addEventListener("click", () => {    resolve("button clicked");  }, { once: true });   return { promise, cancel: () => reject("cancelled") };}
Deferred stateidle

checking…

  1. Create an order token, then settle it from outside.
Try it yourself

withResolvers separates creating the promise from deciding who will settle it.

Promise.withResolvers is Baseline 2024/newer environments. Node 22 has it; the lesson still uses a fallback when needed.

A realistic use is waitForClick(button): create a promise, resolve it when a real click happens, and optionally reject it if the wait is canceled.

Promise.try

FEATURE DETECT

Promise.try(fn, ...args) is a safety net for code that might fail before it returns a promise. It calls fn now, wraps the return value in a promise, and turns a synchronous throw into a rejection. It is an ES2025/newer-browser feature and is not in Node 22, so this lesson uses a small fallback in tests.

Real-life analogyPromise.try is a safety net

Without a safety net, you need one tool for falling now and another for falling later. Promise.try lets both mistakes become promise rejections.

In real life: Trip immediately on stage
In JavaScript: A synchronous throw inside fn
In real life: Slip later on the rope
In JavaScript: A promise returned by fn later rejects
In real life: Same net catches both
In JavaScript: One .catch handles either failure

Where the analogy stops: Promise.try does not make the function's work synchronous. Promise handlers still run later through the promise job queue.

Promise.try safety net
Feature-detected Promise.tryPop out in the code editor (opens in a new tab)JavaScript
function promiseTry(fn, ...args) {  if (typeof Promise.try === "function") {    return Promise.try(fn, ...args);  }  return new Promise((resolve) => resolve(fn(...args)));} promiseTry(readConfig)  .then((value) => console.log(value))  .catch((error) => console.log(error.message));
Run logthrow
  1. Choose a case, then compare starts.
Try it yourself

Promise.try calls the function right away, but turns a synchronous throw into a rejected promise so one catch path can handle both kinds of failure. Promise.resolve().then(fn) starts fn in a later microtask.

Promise.try is ES2025/newer browsers; Node 22 does not include it, so tests use the local fallback.

Compare it with Promise.resolve().then(fn). That pattern also turns a thrown error into a rejection, but it starts fn later in a microtask. Promise.try(fn) starts fn immediately.

Await dictionaries: Promise.allKeyed

PROPOSAL

Arrays are easy to mix up when each position means something different. The TC39 proposal informally known as “Await dictionary of Promises” adds Promise.allKeyed and Promise.allSettledKeyed. It is Stage 3 and no engine ships it yet. The proposal awaits all own enumerable keys, including symbols. The helper below is intentionally smaller and uses Object.keys, so it handles string keys.

Real-life analogyallKeyed is a labeled lunchbox

A lunchbox with labels is harder to mess up than a row of identical bowls. Keyed promise helpers keep the names next to the results.

In real life: Each compartment has a label
In JavaScript: Each property name stays attached to its value
In real life: Rice and fruit don't swap places
In JavaScript: You read result.user, not position 0
In real life: A broken compartment still has a label
In JavaScript: allSettledKeyed reports status by key

Where the analogy stops: The local helper uses Object.keys for clarity. The Stage 3 proposal is broader and includes own enumerable symbol keys too.

Array destructuring can mix labelsPop out in the code editor (opens in a new tab)JavaScript
const [alerts, user] = await Promise.all([  fetchUser(),  fetchAlerts(),]);// Oops: alerts contains the user, and user contains the alerts.
Labeled lunchbox: allKeyed helper
allKeyed helperPop out in the code editor (opens in a new tab)JavaScript
async function allKeyed(object) {  const keys = Object.keys(object);  const values = await Promise.all(keys.map((key) => object[key]));  return Object.fromEntries(keys.map((key, index) => [key, values[index]]));} const dashboard = await allKeyed({  user: fetchUser(),  stats: fetchStats(),  alerts: fetchAlerts(),});
allSettledKeyed helperPop out in the code editor (opens in a new tab)JavaScript
async function allSettledKeyed(object) {  const keys = Object.keys(object);  const results = await Promise.allSettled(keys.map((key) => object[key]));  return Object.fromEntries(keys.map((key, index) => [key, results[index]]));}
Keyed resultsuccess

checking…

  1. Run the helper to keep labels attached.
Try it yourself

The Stage 3 proposal awaits a dictionary and keeps property names. This helper is smaller: it uses Object.keys, so it covers string keys only and skips symbols.

The proposal says own enumerable keys, including symbols. This local helper intentionally uses Object.keys for readability.
The small helperPop out in the code editor (opens in a new tab)JavaScript
async function allKeyed(object) {  const keys = Object.keys(object);  const values = await Promise.all(keys.map((key) => object[key]));  return Object.fromEntries(keys.map((key, index) => [key, values[index]]));} const dashboard = await allKeyed({  user: fetchUser(),  stats: fetchStats(),  alerts: fetchAlerts(),});

Where you’ll use combinators

Combinators make async intent readable. A loader using Promise.all says “this page needs every piece.” A report using allSettled says “show every outcome.” A CDN loader using any says “first success wins.” A timeout preview using race says “the first settlement decides.”

Dashboard loaderPop out in the code editor (opens in a new tab)JavaScript
async function loadDashboard() {  const [user, widgets, flags] = await Promise.all([    loadUser(),    loadWidgets(),    loadFeatureFlags(),  ]);   const uploads = await Promise.allSettled(files.map(uploadFile));  const fastest = await Promise.any(cdnMirrors.map(loadFromMirror));   return { user, widgets, flags, uploads, fastest };}
Timeout races are a preview

Racing a timeout promise is common, but real production cancellation usually adds AbortController so the slower request can be stopped too. The full pattern appears in the Async patterns lesson later in the course.

Timeout race previewPop out in the code editor (opens in a new tab)JavaScript
const timeout = new Promise((_, reject) => {  setTimeout(() => reject(new Error("too slow")), 3000);}); const data = await Promise.race([loadData(), timeout]);

Common misconceptions

Combinators compared
MethodFulfills whenRejects whenBest for
Promise.allEvery input fulfills; values are in input orderThe first input rejectsRequired parallel data
Promise.allSettledEvery input settles with status objectsNever because of an inputComplete reports
Promise.raceThe first settlement is fulfillmentThe first settlement is rejectionFirst response or timeout previews
Promise.anyThe first input fulfillsEvery input rejects, with AggregateErrorFallbacks where one success is enough
  • “Promise.all cancels the rest.” It does not. It rejects early, but other work keeps running unless you cancel it yourself.
  • “Results come back in finish order.” all and allSettled preserve input order.
  • “allSettled gives me values directly.” It gives status objects. Check status before reading value or reason.
  • “race means first success.” race means first settlement. Use any for first success.
  • “New APIs are everywhere.” Feature-detect withResolvers, Promise.try, and proposals such as allKeyed.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict Promise.all order

Run the code in your head. What one line does it print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
const slow = new Promise((resolve) => setTimeout(() => resolve("slow"), 20));
const fast = Promise.resolve("fast");
Promise.all([slow, fast, "plain"]).then((values) => console.log(values.join(",")));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeFirst success wins

    Which value reaches the then handler?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    Promise.any([
      Promise.reject("no A"),
      new Promise((resolve) => setTimeout(() => resolve("yes B"), 5)),
      Promise.resolve("yes C"),
    ]).then((value) => console.log(value));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeReport every upload

      What status string prints?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      Promise.allSettled([Promise.resolve("ok"), Promise.reject("bad")])
        .then((results) => console.log(results.map((item) => item.status).join("/")));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeWrite a keyed wait helper

        Fill in or study the helper, then predict its output.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        async function allKeyed(object) {
          const keys = Object.keys(object);
          const values = await Promise.all(keys.map((key) => object[key]));
          return Object.fromEntries(keys.map((key, index) => [key, values[index]]));
        }
        allKeyed({ user: Promise.resolve("Ada"), count: 3 })
          .then((result) => console.log(result.user + " " + result.count));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeCatch a synchronous throw as a rejection

          What does the fallback catch print?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          function promiseTry(fn) {
            if (typeof Promise.try === "function") return Promise.try(fn);
            return new Promise((resolve) => resolve(fn()));
          }
          promiseTry(() => { throw new Error("sync boom"); })
            .catch((error) => console.log(error.message));

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            8 QUESTIONS
            Promise combinators quiz · 8 questionsScore: first tries count
            1. Question 1 of 8What does Promise.all do when one input rejects?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              Promise.all([Promise.resolve("A"), "B"])
                .then((values) => console.log(values.join("-")));

              Choose an answer to see the explanation.

            3. Question 3 of 8Which combinator never rejects because one input rejected?

              Choose an answer to see the explanation.

            4. Question 4 of 8What happens with Promise.race([])?

              Choose an answer to see the explanation.

            5. Question 5 of 8What does Promise.any reject with when every input rejects?

              Choose an answer to see the explanation.

            6. Question 6 of 8What is Promise.withResolvers useful for?

              Choose an answer to see the explanation.

            7. Question 7 of 8Why feature-detect Promise.try in this lesson?

              Choose an answer to see the explanation.

            8. Question 8 of 8What is true about the allKeyed proposal?

              Choose an answer to see the explanation.

            Key takeaways

            • Promise.all is for “all must succeed.” It preserves input order and rejects with the first rejection reason.
            • Promise.allSettled is for reports. It waits for every input and gives status objects.
            • Promise.race is first settlement; Promise.any is first fulfillment or AggregateError.
            • withResolvers and Promise.try are newer APIs, so feature-detect and use fallbacks when needed.
            • allKeyed is a Stage 3 proposal; a small Object.keys helper can teach the pattern today.

            Final definition: Promise combinators turn many promised outcomes into one promise whose rules match the way your feature wants to wait.

            Up next: Promisification & promise-based APIs.

            CompleteFrontend Clear concepts. Working examples.