Async/Await and Promise.all: Concurrency and Failure

Start independent work together, handle partial failure and distinguish fail-fast from cancellation.

What problem does this solve?

Fetching independent dependencies sequentially adds their latency. Starting everything at once can overload a service. Concurrency is useful only when dependency order, resource limits and failure behavior are explicit.

What is it?

An async function returns a promise. await suspends that function's continuation until settlement; it does not block the entire process. Promise.all combines inputs into one result and rejects when an input rejects.

Mental model

Start several kitchen orders before waiting at the collection counter. Promise.all is the collection ticket: the functions you invoked started the work. An array of uncalled functions does not start anything.

How it works internally

Promise.all subscribes to its inputs and preserves input order in its fulfillment array, regardless of completion order. A first rejection rejects the combined promise but does not cancel other operations. await resumes through promise scheduling. Promise.allSettled is appropriate when every outcome is needed.

Production example

An account summary can load profile and permissions concurrently when neither depends on the other. If permissions are required to authorize a sensitive request, keep that check ahead of sensitive side effects rather than parallelizing blindly.

Node.js / TypeScript implementation

This runnable example demonstrates that result order follows input order. Real HTTP requests should add deadlines and check response.ok.

async function run() {
  const slow = new Promise<string>(resolve =>
    setTimeout(() => resolve("profile"), 20));
  const fast = Promise.resolve("permissions");
  const [profile, permissions] = await Promise.all([slow, fast]);
  console.log(profile, permissions);
}
run().catch(console.error);

Common mistakes

Array.forEach does not await async callbacks. Using Promise.all on a very large array still starts all mapped operations immediately. Catching every error and returning undefined can turn a failed required dependency into apparent success.

Performance / reliability considerations

Set a concurrency limit for bulk work. Cancellation requires cooperation, such as an AbortSignal passed into fetch; rejecting a wrapper promise alone does not stop the underlying operation. Give independent operations independent transactions unless they truly share one atomic boundary.

Interview questions

Does Promise.all start functions or observe promises? Does rejection cancel fetch? When would allSettled be preferable? Why can async forEach finish before its callbacks?

Quick Revision

Invoke independent operations before awaiting. Promise.all returns results in input order and fails fast without cancellation. Await pauses one async function. Use allSettled for complete outcome reporting, bounded workers for volume and AbortSignal for cooperative deadlines. Keep required authorization ahead of side effects.

Further reading

MDN: Promise.all ↗

MDN: async functions ↗