Recipe · Functions

How do I type an async generator in JSDoc?

  • Node.js (ES modules)
  • AsyncGenerator
  • for await
  • @returns
  • Checked with TypeScript 5.9

The problem I page through an API with an async generator function and loop over it with for await. What do I put in @returns, and does the @yields tag do anything?

The short answer

Give the async function* a @returns {AsyncGenerator<Order, void, undefined>}, and loop over its result with for await:

pages.js pages.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 {object} Order
 * @property {string} id
 * @property {number} total
 */

/**
 * @param {number} page
 * @returns {Promise<{ orders: Order[], next: number | null }>}
 */
async function fetchPage(page) {
  const response = await fetch(`https://shop.example/api/orders?page=${page}`);
  return response.json();
}

/** @returns {AsyncGenerator<Order, void, undefined>} */
export async function* allOrders() {
  /** @type {number | null} */
  let page = 1;
  while (page !== null) {
    const { orders, next } = await fetchPage(page);
    yield* orders;
    page = next;
  }
}

let revenue = 0;
for await (const order of allOrders()) {
  revenue += order.total;
}
console.log(revenue);
interface Order {
  id: string;
  total: number;
}

async function fetchPage(page: number): Promise<{ orders: Order[]; next: number | null }> {
  const response = await fetch(`https://shop.example/api/orders?page=${page}`);
  return response.json();
}

export async function* allOrders(): AsyncGenerator<Order, void, undefined> {
  let page: number | null = 1;
  while (page !== null) {
    const { orders, next } = await fetchPage(page);
    yield* orders;
    page = next;
  }
}

let revenue = 0;
for await (const order of allOrders()) {
  revenue += order.total;
}
console.log(revenue);
An async generator typed with @returns {AsyncGenerator<…>}.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": ["node"]
  },
  "include": ["*.js"]
}

Every yield (and yield*) is checked against Order, and the loop variable order is an Order, so order.total is checked at the call site too.

Why it works

An async generator function returns an AsyncGenerator object, which is both an async iterator (it has next(), returning promises) and an async iterable (for await can loop over it). Its three type arguments are what it yields, what it returns when it finishes, and what next() accepts. for await unwraps each promise, so the loop sees plain Order values.

The same thing in TypeScript

The TypeScript equivalent tab writes async function* allOrders(): AsyncGenerator<Order, void, undefined>. Both forms can also leave the annotation out: tsc infers the yield type from the yield expressions.

What goes wrong

Yield something else and the yield is the error (TS2322):

pages.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: pages.js(7,9): error TS2322: Type 'string' is not assignable to type 'Order'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. pages.js(7,9): error TS2322: Type 'string' is not assignable to type 'Order'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. pages.js(7,9): error TS2322: Type 'string' is not assignable to type 'Order'. checked 21 October 2026
/** @typedef {{ id: string, total: number }} Order */

/** @returns {AsyncGenerator<Order, void, undefined>} */
export async function* allOrders() {
  yield { id: "A-1001", total: 40 };
  yield "A-1002";error TS2322: Type 'string' is not assignable to type 'Order'.
}
Yielding something that is not an Order.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": ["node"]
  },
  "include": ["*.js"]
}

Forget the await in the loop and tsc reports that the generator is not iterable the synchronous way (TS2488):

pages.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: pages.js(9,21): error TS2488: Type 'AsyncGenerator<Order, void, undefined>' must have a '[Symbol.iterator]()' method that returns an iterator. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. pages.js(9,21): error TS2488: Type 'AsyncGenerator<Order, void, undefined>' must have a '[Symbol.iterator]()' method that returns an iterator. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. pages.js(9,21): error TS2488: Type 'AsyncGenerator<Order, void, undefined>' must have a '[Symbol.iterator]()' method that returns an iterator. checked 21 October 2026
/** @typedef {{ id: string, total: number }} Order */

/** @returns {AsyncGenerator<Order, void, undefined>} */
export async function* allOrders() {
  yield { id: "A-1001", total: 40 };
}

for (const order of allOrders()) {error TS2488: Type 'AsyncGenerator<Order, void, undefined>' must have a '[Symbol.iterator]()' method that returns an iterator.
  console.log(order.total);
}
A plain for...of over an async generator.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": ["node"]
  },
  "include": ["*.js"]
}

Gotchas

  • @yields is ignored. It is a documentation tag from JSDoc; TypeScript does not check it. Put the yield type in @returns.
  • yield* delegates element by element. yield* orders yields each order of the array, so the array’s element type must match the yield type.
  • for await needs a module or an async function. At the top level it works in an ES module, as in the first example; inside a function, the function must be async.

Questions

Can I write @returns {AsyncIterable<Order>} instead?

Yes. AsyncIterable<Order> (or AsyncIterableIterator<Order>) is a looser type that hides the generator's return and throw methods from callers; it is enough when callers only loop with for await.

What are the second and third type arguments of AsyncGenerator?

The type the generator returns when it finishes (void here) and the type callers may pass to next() (undefined when nobody does). Synchronous generators use Generator<T, TReturn, TNext> the same way.

Does @yields type what the generator yields?

No. TypeScript does not read @yields; the yield type comes from @returns (or is inferred from the yield expressions).

Sources

Last reviewed 21 October 2026.