Type expression

Promise and async functions

  • Collections
  • Tested with TypeScript 5.9

Promise<T> is the type of a promise that resolves to a T. An async function always returns one, so its JSDoc says @returns {Promise<T>} and its return statements return plain T values. await unwraps the type again: await loadProducts() is Product[].

Because the promise and its value have different types, a forgotten await is caught: reading .length from a Promise<string[]> is TS2339, as the second example shows.

At a glance

Use JSDoc in a .js file TypeScript in a .ts file
An async function /** @returns {Promise<Product[]>} */ async function load() {} async function load(): Promise<Product[]> {}
A promise parameter /** @param {Promise<Response>} pending */ (pending: Promise<Response>)
A promise of nothing /** @returns {Promise<void>} */ (): Promise<void>

Examples

catalog.js catalog.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 {{ sku: string, name: string, price: number }} Product */

/**
 * @param {string} category
 * @returns {Promise<Product[]>}
 */
export async function loadProducts(category) {
  const response = await fetch(`/api/products?category=${encodeURIComponent(category)}`);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return /** @type {Product[]} */ (await response.json());
}

loadProducts("shirts").then((products) => products.map((p) => p.name));
type Product = { sku: string; name: string; price: number };

export async function loadProducts(category: string): Promise<Product[]> {
  const response = await fetch(`/api/products?category=${encodeURIComponent(category)}`);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return (await response.json()) as Product[];
}

loadProducts("shirts").then((products) => products.map((p) => p.name));
Typing an async function and its JSON.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
catalog.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: catalog.js(9,15): error TS2339: Property 'length' does not exist on type 'Promise<string[]>'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. catalog.js(9,15): error TS2339: Property 'length' does not exist on type 'Promise<string[]>'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. catalog.js(9,15): error TS2339: Property 'length' does not exist on type 'Promise<string[]>'. checked 21 October 2026
/** @returns {Promise<string[]>} */
async function loadSkus() {
  return ["TEE-01", "MUG-07"];
}

export async function countSkus() {
  const skus = loadSkus();
  return skus.length;error TS2339: Property 'length' does not exist on type 'Promise<string[]>'.
}
Forgetting await: a Promise is not its value.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
delay.js 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
/**
 * @param {number} ms
 * @returns {Promise<void>}
 */
export function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * @template T
 * @param {Promise<T>} work
 * @param {number} ms
 * @returns {Promise<T>}
 */
export function withTimeout(work, ms) {
  return Promise.race([work, delay(ms).then(() => Promise.reject(new Error("Timed out")))]);
}

withTimeout(fetch("/api/cart"), 5000).then((response) => response.status);
Creating a promise with new Promise.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Typing JSON from fetch

response.json() returns Promise<any>, so its result is unchecked. The first example casts it to the expected type with /** @type {Product[]} */ (await response.json()). The cast is a claim, not a check; for data you do not control, cast to unknown and validate instead.

new Promise

A function that builds a promise with new Promise declares the resolved type in @returns, as delay() does with Promise<void>; TypeScript then checks what resolve is called with. Generic helpers such as withTimeout take @template T so the result keeps the input's type.

Gotchas

  • An async function's return type must be written as Promise<T>; @returns {T} alone is TS1064.
  • Promise.all([a, b]) is typed as a tuple of the resolved types, so destructuring its result keeps each type.

Questions

How do I document the return type of an async function in JSDoc?

@returns {Promise<T>}, where T is the resolved value. For an async function that resolves to nothing, @returns {Promise<void>}.

Sources

Last reviewed 20 October 2026.