C.W.K.
Stream
Lesson 04 of 05 · published

`Promise.all` and Tuple Typing

~8 min · async-promises, promise-all, tuples, concurrency

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"Promise.all is parallelism with tuples preserved."

What Promise.all does

Promise.all([p1, p2, p3]) takes an array (or tuple) of Promises and returns a Promise that resolves when all the input Promises resolve. The resolved value is an array of the resolved values, in the same order.

At the type level, when you pass a tuple, the result preserves the tuple shape: Promise.all([Promise.resolve(1), Promise.resolve('a')]) is Promise<[number, string]>. This means destructuring after await preserves each position's type — [a, b] gets a: number; b: string.

Sequential vs parallel

Two awaited calls in a row run sequentially. const a = await fa(); const b = await fb() takes time(fa) + time(fb). Promise.all runs in parallel: const [a, b] = await Promise.all([fa(), fb()]) takes max(time(fa), time(fb)).

For independent async calls, Promise.all is almost always the right move. The compiler can't tell whether your calls are independent — that's your job.

Failure behavior

Promise.all rejects fast: if any input Promise rejects, the whole thing rejects with that error. For 'wait for all, collect both successes and failures,' use Promise.allSettled (returns an array of { status: 'fulfilled' | 'rejected', value? / reason? }).

If your async calls don't depend on each other's results, run them in parallel with Promise.all. The performance improvement is real; the type ergonomics (tuple preservation) make it the cleanest option.

Code

Parallel vs sequential, with type-safe tuples·typescript
// Sequential — slower.
async function loadSequential() {
  const user = await fetchUser(1);        // wait
  const posts = await fetchPosts(1);      // then wait again
  return { user, posts };
}

// Parallel — Promise.all preserves tuple typing.
async function loadParallel() {
  const [user, posts] = await Promise.all([
    fetchUser(1),
    fetchPosts(1),
  ]);
  // user: User, posts: Post[] — types preserved per position
  return { user, posts };
}

// Promise.allSettled — collects all results, including failures.
async function loadResilient() {
  const results = await Promise.allSettled([fetchUser(1), fetchPosts(1)]);
  // results: [PromiseSettledResult<User>, PromiseSettledResult<Post[]>]
  for (const r of results) {
    if (r.status === 'fulfilled') console.log(r.value);
    else console.error(r.reason);
  }
}

External links

Exercise

Refactor a function that does three sequential fetches into one that uses Promise.all. Measure the difference (with performance.now()). Then change one fetch to reject and observe how Promise.all behaves — is the error message clear?
Hint
Sequential is sum of times; parallel is max. The rejection in Promise.all surfaces the first rejector's reason — for clearer error attribution, use Promise.allSettled and check each result.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.