All topics
Asyncadvanced

Async Iterators and for-await-of

Iterating over sequences of values that arrive asynchronously, such as paginated APIs or streamed data, using async generators and for-await-of.

An async iterator produces values that arrive over time, and for await...of consumes them one at a time.

for await...of is like reading letters arriving one by one over several days, versus for...of reading a stack already sitting on your desk.

Key Concepts

1
    async function* pages(url) {
      let next = url;
      while (next) {
        const res = await fetch(next);
        const data = await res.json();
        yield* data.items;          // yield each item
        next = data.nextPage;
      }
    }
2
    for await (const item of pages('/api/items')) {
      process(item);
    }
3
What that buys. The consumer sees a flat sequence of items. Pagination — fetching, checking for a next page, looping — is hidden in the generator.
4
Without it, the caller manages the cursor, and every consumer of that API repeats the same loop.
5
It is lazy, which is the memory argument. The next page is not fetched until the loop asks for it. A million-item dataset is processed without ever holding a million items.
6
    // eager -- everything in memory
    const all = await fetchAllPages();
    all.forEach(process);
7
    // lazy -- one page at a time
    for await (const item of pages(url)) process(item);
8
break stops the fetching, because the generator is suspended and never resumed. Finding what you need on page two means pages three onward are never requested.
break
9
The distinction from a sync generator.
    function*       yields values now
    async function* yields promises; for await unwraps them
10
Node streams are async iterable, which is where most people meet this:
11
    for await (const chunk of fs.createReadStream('big.log')) {
      process(chunk);
    }
12
Reading a file larger than memory, line by line, with ordinary loop syntax.
13
The caution: it is sequential by design.
    for await (const url of urls) {
      await fetch(url);        // one at a time
    }
14
If the items are independent and you want concurrency, this is the wrong tool — that is Promise.all over a batch. for await is for a genuine sequence where order or backpressure matters.
Promise.allfor await
15
Cleanup runs on early exit. A finally inside the generator executes when the loop breaks, which is how a stream or a connection gets closed.
finally
16
What the interviewer is probing.1. "When would you use an async iterator?" Probing: the use case. Stalls: "Instead of a loop." Moves up: a sequence that arrives over time — paginated API results or a stream — consumed without buffering it all.
17
2. "What does for-await-of do with an array of promises?" Probing: the behaviour. Stalls: "Runs them in parallel." Moves up: it awaits each in turn, serialising them — use Promise.all when they are independent.
18
3. "What makes an object async iterable?" Probing: the protocol. Stalls: "A then method." Moves up: a Symbol.asyncIterator method returning an object whose next returns a promise of a result.
19
4. "How do you write one easily?" Probing: the syntax. Stalls: "By hand." Moves up: an async generator — async function* — where each yield becomes a value in the sequence.