Promise Coordination

The Course
•
4 min read

Start independent work before waiting for it

await first(); await second(); starts second only after first fulfills. That is right when the second needs the first result. If they are independent, call both first, then use Promise.all to await their combined results. This is overlapping work, not a promise that JavaScript runs two CPU-heavy loops on separate cores.

Promise.all preserves input order in its result array, even if operations finish in another order. It rejects when one input rejects. It does not automatically cancel the other operations. A partially completed write may already have happened, so do not confuse rejection with rollback.

Promise.allSettled waits for every outcome. Each record has status “fulfilled” and value, or status “rejected” and reason. Use it when partial results are useful, such as a dashboard with independent panels. For a list that must be complete, an all-or-nothing failure can be clearer.

Always choose the product behavior before selecting the promise utility. A catch that replaces every error with an empty array makes failures look like “no results.” Keep the difference visible. The tests below use immediately settled local promises, so their output does not depend on a remote machine’s speed.

Predict and trace

Write the output before running. Follow the values through each call; if your prediction differs, identify the first step that surprised you.

Predict, then run JavaScript
async function main() {
  // This result needs both requests to succeed.
  const values = await Promise.all([
    Promise.resolve("books"),
    Promise.resolve("tags")
  ]);

  console.log(values.join(", "));

  // Here we keep each outcome, including the failure.
  const outcomes = await Promise.allSettled([
    Promise.resolve(2),
    Promise.reject(new Error("Unavailable"))
  ]);

  console.log(outcomes[0].status);
  console.log(outcomes[1].status);
  console.log(outcomes[1].reason.message);
}

main();
Check your prediction
books, tags
fulfilled
rejected
Unavailable

The rejected promise is observed by allSettled rather than left unhandled.

Starting work and awaiting work are different steps

Promise.all cannot start work hidden inside an uncalled function. The calls create promises; the coordination helper then observes their outcomes. Sequential await is correct for dependencies, but needlessly serializes independent operations.

The result array preserves input order. A rejection does not undo side effects from other operations and does not cancel them. Use allSettled when every outcome is needed, and inspect each status before accessing value or reason.

Starting work and awaiting work are different steps: trace JavaScript
async function main() {
  const events = [];

  async function task(name) {
    events.push(name);
    return name;
  }

  const a = task("A");
  const b = task("B");

  console.log(events.join(","));
  console.log((await Promise.all([b, a])).join(","));
}

main();
Check your prediction
A,B
B,A

Call order created A then B, but the input order supplied to all is B then A. Completion timing is a third concept; do not confuse any of the three.

Starting work and awaiting work are different steps: contrast JavaScript
async function main() {
  const outcomes = await Promise.allSettled([
    Promise.resolve(0),
    Promise.reject("offline")
  ]);

  for (const item of outcomes) {
    console.log(item.status === "fulfilled" ? item.value : item.reason);
  }
}

main();
Check your prediction
0
offline

Zero is a successful value. Branching on status preserves it, while a truthiness test on value could mistake it for failure.

Try the rule on new inputs

Try one rejection with Promise.all and explain what its caller receives. Then use allSettled and retain the successful zero. Avoid adding timers merely to guess completion order; controlled promises will make schedule tests precise.

Build it yourself

Exercise

Promise Coordination: practise

Write successfulValues(promises), awaiting all outcomes and returning only fulfilled values in input order. Print 2, 5 for one rejection between two successes.

Your working space JavaScript
// Inspect status before reading value.
Hint 1: the idea

AllSettled records are ordinary objects with different fields by status.

Hint 2: a first step

Filter fulfilled outcomes, then map their value properties.

Show one solution
async function main() {
  async function successfulValues(promises) {
    const outcomes = await Promise.allSettled(promises);

    return outcomes
      .filter(item => item.status === "fulfilled")
      .map(item => item.value);
  }

  const values = await successfulValues([
    Promise.resolve(2),
    Promise.reject(new Error("bad")),
    Promise.resolve(5)
  ]);

  console.log(values.join(", "));
}

main();
2, 5

Try an empty input and all failures. This helper deliberately discards failure information; adapt it to return a separate error count when the interface needs to report omissions.

Test a changed requirement

Try an empty input and all failures. This helper deliberately discards failure information; adapt it to return a separate error count when the interface needs to report omissions.

Explain before moving on

Which operations may run after Promise.all rejects? When is partial success misleading?

Was this article helpful?

Your response is saved on this device.