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

Async Iterables: `for await`

~9 min · async-promises, async-iterable, for-await, streaming

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"A Promise per value. A stream the type system understands."

What async iterables are

An async iterable is an object with a [Symbol.asyncIterator]() method that returns an object with an async next(): Promise<{ value: T; done: boolean }> method. for await (const x of stream) walks through it, awaiting each value before continuing.

This is how Node.js streams, file line iterators, and HTTP-server request bodies are typed. Anywhere you have a series of values that arrive over time, async iterables are the canonical shape.

Generators that yield Promises

The easiest way to create an async iterable is an async generator: async function* gen(): AsyncIterable<number> { yield 1; yield 2; yield await fetchValue() }. Each yield produces one value of the stream; awaiting inside the generator pauses until the value is ready.

Difference from Promise<T[]>

A Promise<T[]> is one Promise that resolves to the full array. An AsyncIterable<T> is a stream — values arrive one at a time, and you can start processing the first before the last has arrived. For large data or slow producers, async iterables let you stream; for small batched data, a Promise<T[]> is simpler.

Async iterables are the type-system handle for streams. When values arrive over time, reach for AsyncIterable<T> and for await. When all the values arrive together, reach for Promise<T[]>.

Code

Async iterables — generator and Node streams·typescript
// Async generator — yields values over time.
async function* tickStream(): AsyncIterable<number> {
  for (let i = 0; i < 5; i++) {
    await new Promise((r) => setTimeout(r, 100));
    yield i;
  }
}

// Consume with for-await.
async function consume() {
  for await (const n of tickStream()) {
    console.log(n);    // n: number, printed every 100ms
  }
}

// fs streams, Node's readline, fetch's body — all are async iterables.
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';

async function readLines(path: string) {
  const rl = createInterface({ input: createReadStream(path) });
  for await (const line of rl) {
    console.log(line);   // line: string
  }
}

External links

Exercise

Write an async generator paginate<T>(fetchPage: (cursor: string | null) => Promise<{ items: T[]; next: string | null }>): AsyncIterable<T> that yields items from successive pages until next is null. Then consume it with for await.
Hint
Each iteration: await fetchPage(currentCursor), yield each item, update currentCursor from .next. Stop when next is null. The consumer never sees pagination — just a stream of items.

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.