Recipe · Functions

How do I type the results of Promise.all in JavaScript?

  • Node.js (ES modules)
  • Promise.all
  • Tuples
  • Promise.allSettled
  • Checked with TypeScript 5.9

The problem I load a customer and their orders in parallel with Promise.all. Sometimes the destructured results have the right types and sometimes they are a union of both. What decides that?

The short answer

Type the functions that make the promises, and pass the promises to Promise.all as an array literal. The awaited result is a tuple with each type in its place:

dashboard.js dashboard.ts TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/** @typedef {{ id: string, name: string }} Customer */
/** @typedef {{ id: string, total: number }} Order */

/** @param {string} id @returns {Promise<Customer>} */
async function loadCustomer(id) {
  return { id, name: "Ada" };
}

/** @param {string} customerId @returns {Promise<Order[]>} */
async function loadOrders(customerId) {
  return [{ id: `${customerId}-1`, total: 40 }];
}

/** @param {string} id */
export async function loadDashboard(id) {
  const [customer, orders] = await Promise.all([loadCustomer(id), loadOrders(id)]);
  return { name: customer.name, spent: orders.reduce((sum, order) => sum + order.total, 0) };
}
type Customer = { id: string; name: string };
type Order = { id: string; total: number };

async function loadCustomer(id: string): Promise<Customer> {
  return { id, name: "Ada" };
}

async function loadOrders(customerId: string): Promise<Order[]> {
  return [{ id: `${customerId}-1`, total: 40 }];
}

export async function loadDashboard(id: string) {
  const [customer, orders] = await Promise.all([loadCustomer(id), loadOrders(id)]);
  return { name: customer.name, spent: orders.reduce((sum, order) => sum + order.total, 0) };
}
Promise.all with an array literal keeps each type in its place.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": ["node"]
  },
  "include": ["*.js"]
}

customer is a Customer and orders an Order[], so customer.name and order.total are checked with no annotation on either.

Why it works

Promise.all’s type in TypeScript’s library is generic over the array it receives. Given an array literal, tsc infers a tuple, [Promise<Customer>, Promise<Order[]>], and maps it to [Customer, Order[]] with Awaited. Destructuring then takes each element’s own type.

The same thing in TypeScript

The TypeScript equivalent tab is the same code with the parameter and return types written inline. Nothing about Promise.all changes; the inference is identical.

What goes wrong

Build the array first and the tuple is lost. A variable initialized with an array literal is an array, here (Promise<Customer> | Promise<Order[]>)[], so every element is the union of both, and both property reads fail with TS2339:

dashboard.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: dashboard.js(19,27): error TS2339: Property 'name' does not exist on type 'Customer | Order[]'. Property 'name' does not exist on type 'Order[]'. dashboard.js(19,47): error TS2339: Property 'length' does not exist on type 'Customer | Order[]'. Property 'length' does not exist on type 'Customer'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. dashboard.js(19,27): error TS2339: Property 'name' does not exist on type 'Customer | Order[]'. Property 'name' does not exist on type 'Order[]'. dashboard.js(19,47): error TS2339: Property 'length' does not exist on type 'Customer | Order[]'. Property 'length' does not exist on type 'Customer'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. dashboard.js(19,27): error TS2339: Property 'name' does not exist on type 'Order[] | Customer'. Property 'name' does not exist on type 'Order[]'. dashboard.js(19,47): error TS2339: Property 'length' does not exist on type 'Order[] | Customer'. Property 'length' does not exist on type 'Customer'. checked 21 October 2026
/** @typedef {{ id: string, name: string }} Customer */
/** @typedef {{ id: string, total: number }} Order */

/** @param {string} id @returns {Promise<Customer>} */
async function loadCustomer(id) {
  return { id, name: "Ada" };
}

/** @param {string} customerId @returns {Promise<Order[]>} */
async function loadOrders(customerId) {
  return [{ id: `${customerId}-1`, total: 40 }];
}

/** @param {string} id */
export async function loadDashboard(id) {
  const jobs = [loadCustomer(id), loadOrders(id)];
  const [customer, orders] = await Promise.all(jobs);
  return { name: customer.name, count: orders.length };error TS2339: Property 'name' does not exist on type 'Customer | Order[]'.
  Property 'name' does not exist on type 'Order[]'.error TS2339: Property 'length' does not exist on type 'Customer | Order[]'.
  Property 'length' does not exist on type 'Customer'.
}
The same promises in an array variable first.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": ["node"]
  },
  "include": ["*.js"]
}

Pass the literal straight to Promise.all, or give the variable a tuple type: /** @type {[Promise<Customer>, Promise<Order[]>]} */.

Promise.allSettled never rejects; each result says whether its promise was fulfilled or rejected. The result is a union of two shapes, and only the fulfilled one has value:

refresh.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: refresh.js(11,22): error TS2339: Property 'value' does not exist on type 'PromiseSettledResult<number>'. Property 'value' does not exist on type 'PromiseRejectedResult'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. refresh.js(11,22): error TS2339: Property 'value' does not exist on type 'PromiseSettledResult<number>'. Property 'value' does not exist on type 'PromiseRejectedResult'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. refresh.js(11,22): error TS2339: Property 'value' does not exist on type 'PromiseSettledResult<number>'. Property 'value' does not exist on type 'PromiseRejectedResult'. checked 21 October 2026
/** @param {string} url @returns {Promise<number>} */
async function ping(url) {
  const started = Date.now();
  await fetch(url, { method: "HEAD" });
  return Date.now() - started;
}

const results = await Promise.allSettled([ping("https://example.com/"), ping("https://example.org/")]);
for (const result of results) {
  console.log(result.value);error TS2339: Property 'value' does not exist on type 'PromiseSettledResult<number>'.
  Property 'value' does not exist on type 'PromiseRejectedResult'.
  if (result.status === "fulfilled") console.log(`${result.value} ms`);
  else console.log(`failed: ${result.reason}`);
}
Promise.allSettled results need narrowing by status.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": ["node"]
  },
  "include": ["*.js"]
}

Checking result.status === "fulfilled" narrows the union, so result.value is a number in that branch and result.reason is available in the other.

Gotchas

  • reason is any. A rejection can carry anything, so check it (reason instanceof Error) before reading message.
  • /** @type {const} */ works too. Casting the array with /** @type {const} */ ([loadCustomer(id), loadOrders(id)]) keeps it a tuple, the JSDoc form of as const.
  • Order follows the array, not completion. The results come back in the order of the input array, whichever promise settles first.

Questions

Do I need any JSDoc for Promise.all?

Usually not. Type the functions that return the promises; Promise.all keeps their types position by position as long as it receives an array literal.

What does Promise.race return?

A promise of the union of the input types: racing a Promise<Customer> and a Promise<Order[]> gives Promise<Customer | Order[]>, because either one may win.

Sources

Last reviewed 21 October 2026.