Use Promise All in TypeScript

Learn to use Promise.all with TypeScript's type system. Covers typed tuple results, Promise.allSettled for partial failures, and avoiding common type pitfalls.

7 min read

Promise.all runs multiple promises concurrently and waits for all of them to complete. In TypeScript, it does more than that: it preserves the exact type of each input promise in the corresponding position of the result tuple. This means you can run three different API calls in parallel and get three correctly typed values back without a single type assertion.

Basic Typed Usage

Pass an array of promises to Promise.all, and TypeScript infers a tuple where each element keeps its input type.

typescripttypescript
const [user, posts, settings] = await Promise.all([
  fetchUser(1),
  fetchPosts(1),
  fetchSettings(),
]);

The variable user gets the User type, posts gets an array of Post, and settings gets the Settings type. TypeScript reads the return type of each function and builds the tuple automatically. You do not annotate anything.

This works because TypeScript infers the array literal as a tuple when passed to Promise.all. The inference is positional: the first promise's resolved type goes into position zero of the result, the second into position one, and so on.

Mixed Return Types in the Result

The real power of typed Promise.all appears when the input promises return different types. TypeScript tracks each one independently.

typescripttypescript
const [count, name, isActive] = await Promise.all([
  Promise.resolve(42),
  Promise.resolve("Alex"),
  Promise.resolve(true),
]);

Count is a number, name is a string, and isActive is a boolean. Each variable has the exact type of its corresponding input, inferred without any annotation. This is far more precise than what you get from running the promises sequentially, where you would need separate await statements.

How Rejection Works

Promise.all has an important behavior: if any input promise rejects, the entire result rejects immediately. The other promises continue executing in the background, but their results are lost.

typescripttypescript
try {
  const results = await Promise.all([
    fetchUser(1),
    fetchUser(999), // This one fails with 404
    fetchPosts(1),   // Never reached
  ]);
} catch (error) {
  // Only the first rejection is caught
}

The type system reflects this. The return type of Promise.all is a promise of the tuple, not a partial result. If you need all results regardless of failures, use Promise.allSettled instead.

Using Promise.allSettled for Partial Failures

Promise.allSettled waits for every promise to settle, whether it fulfills or rejects. The result is an array of objects, each with a status field.

typescripttypescript
const results = await Promise.allSettled([
  fetchUser(1),
  fetchUser(999),
  fetchPosts(1),
]);

Each result in the array is a PromiseSettledResult object. You must narrow on the status field to determine whether that particular promise fulfilled or rejected before accessing value or reason.

typescripttypescript
results.forEach((result) => {
  if (result.status === "fulfilled") {
    console.log(result.value);
  } else {
    console.error(result.reason);
  }
});
Promise.all vs Promise.allSettled behavior

The diagram shows the fundamental difference. Promise.all is all-or-nothing. Promise.allSettled always resolves and gives you per-promise status information. Choose allSettled when you want to process successful results even if some requests fail.

Typing Promise.allSettled Results

The result type of allSettled is PromiseSettledResult for each input. For fulfilled promises, you get a PromiseFulfilledResult with a value. For rejected ones, you get a PromiseRejectedResult with a reason.

typescripttypescript
const [r1, r2] = await Promise.allSettled([
  Promise.resolve(10),
  Promise.reject(new Error("fail")),
]);
 
if (r1.status === "fulfilled") {
  // r1.value is number
}
if (r2.status === "rejected") {
  // r2.reason is unknown
}

The reason is typed as any, the same as the reason parameter in a promise's .catch() method. Narrow it with instanceof before accessing message or other properties anyway, since the compiler will not stop unsafe access on its own. The value on fulfilled results retains the original promise's resolved type.

Avoiding Common Pitfalls

Using Promise.all with a dynamic array. When you spread an array into Promise.all, TypeScript loses the tuple inference and widens to an array type.

typescripttypescript
const promises = [fetchUser(1), fetchUser(2)];
// promises is typed as Promise<User>[]
 
const users = await Promise.all(promises);
// users is User[], not [User, User]

To preserve the tuple type when using a variable, annotate it with as const, or pass the array literal directly to Promise.all without an intermediate variable.

Assuming allSettled reason is safe to use directly. The reason in a rejected allSettled result is typed any, not Error, so the compiler will not flag unchecked access. Narrow it with instanceof before accessing properties.

Not handling the rejection case of Promise.all. Wrapping Promise.all in try/catch is necessary because a single failure rejects the entire operation. If any of your inputs can fail, plan for the catch path.

For more on error handling patterns, see handle API errors in TypeScript. For canceling in-flight requests, see cancel async requests in TypeScript. For typing the resolved values, see type promise return values.

Rune AI

Rune AI

Key Insights

  • Promise.all returns a typed tuple preserving each input promise's resolved type.
  • Destructuring the result gives each variable its correct individual type.
  • Promise.all rejects immediately if any input rejects, losing all other results.
  • Use Promise.allSettled when partial failures are expected and acceptable.
  • Narrow allSettled results on status to access value or reason safely.
RunePowered by Rune AI

Frequently Asked Questions

Does Promise.all preserve types for each position?

Yes. When you pass an array of different-typed promises to Promise.all, TypeScript infers a tuple type where each position retains the type of its corresponding input. Destructuring [a, b] gives you the correct types for each variable.

What happens if one promise in Promise.all rejects?

Promise.all rejects immediately with the first rejection reason. The other promises continue executing but their results are lost. Use Promise.allSettled if you need all results regardless of individual failures.

How do I type the result of Promise.allSettled?

TypeScript types the result as an array of PromiseSettledResult objects. Each has a status field. Fulfilled results have a value property. Rejected results have a reason property. Narrow on status to access the correct one.

Conclusion

Promise.all in TypeScript gives you a typed tuple where each position keeps the type of its corresponding input promise. This eliminates manual type assertions when running concurrent requests. For cases where partial failures are acceptable, Promise.allSettled provides typed result objects that you narrow by status. The key insight is that the type system reflects the runtime behavior exactly: all-or-nothing for all, and always-resolves for allSettled.