Skip to content
TaeyoungKim.dev

Does Promise.all stop other tasks when one promise rejects?

WebWritten 3 min readTaeyoungKim
LinkedInX

Two file reads begin together, and the first fails. The Promise.all call enters catch, so did the second read stop too? Watch the log a little longer: the second task finishes. Failure of the combined result is different from cancellation of tasks already started.

A Promise.all rejection does not cancel its inputs

In the diagram, A fails first and the combined promise rejects. B was already running, so its later completion still appears; rejection is not an automatic cancellation signal.

Promise.all([a, b]) fulfills with values in input order when both inputs fulfill. If either rejects, the combined promise rejects with that reason. It does not send a stop signal to the other input promises.

This example uses timers instead of real files or network requests so that both outcomes are easy to observe:

javascript
async function main() {
  const failed = new Promise((_, reject) => {
    setTimeout(() => {
      console.log("A failed");
      reject(new Error("A unavailable"));
    }, 10);
  });

  const slow = new Promise((resolve) => {
    setTimeout(() => {
      console.log("B finished");
      resolve("B result");
    }, 30);
  });

  try {
    await Promise.all([failed, slow]);
  } catch (error) {
    console.log("all rejected:", error.message);
  }

  await slow;
}

main();
text
A failed
all rejected: A unavailable
B finished

The final await slow lets us observe B's completion; it does not start B again. slow was created before it was passed to Promise.all. A's failure makes the combined result unusable, but B's timer remains active.

How should you interpret a late completion log?

When a completion log appears after an “overall failure,” it can look like a duplicate retry. Label the start, success, and failure logs with the same request identifier and a separate task name to see the ordering. Avoid logging sensitive response bodies or authorization headers.

Test a fast failure alongside a slow success and confirm that the slow task's effect remains after the combined catch runs. That matters when the slow task changes a database or sends a message. Retrying the entire group immediately in catch may overlap new work with an earlier task that is still running.

Do you need every result or actual cancellation?

If you need each task's final outcome, consider Promise.allSettled. It waits for every input and returns entries such as { status: "fulfilled", value } and { status: "rejected", reason }. It is not a cancellation mechanism either.

If one failure must stop the other operations, design around cancellation support in those operations. For example, fetch can receive an abort signal, but that does not guarantee rollback of a request the server has already processed. Server-side work may also need idempotency keys or compensation rules. “Report a failure quickly” and “stop remaining work” are separate requirements.

Key takeaways

Promise.all rejects its returned promise when an input rejects, but it does not cancel the other inputs. Use allSettled when you need all final outcomes; use each operation's own cancellation contract when you need to stop it. Before retrying parallel work with side effects, check whether earlier tasks have really finished.

Author

TaeyoungKim

Connecting technical foundations with implementation, verification, and production decisions.

#JavaScript#Promise.all#Promise.allSettled#Async#Error handling

Read next