Useful Cheatsheetsusefulcheatsheets.com
JavaScript Async Iterators
Chapter 25 · Page 198
Advanced

JavaScript Async Iterators

Learn async iterators, async generators, and for await...of for consuming asynchronous data lazily.

TL;DR

  1. 01Yield promises that resolve to { value, done } via async iterators.
  2. 02Build async generators with async function* to yield values lazily.
  3. 03Consume async iterables with for await...of as values arrive.

Tips

  1. 01Use async generators to wrap paginated APIs, so callers loop over pages without managing cursors manually.
  2. 02Prefer for await...of over manually calling next() when consuming streams — it awaits values and cleans up automatically.

Warnings

  1. 01Awaiting each next() call sequentially means items resolve one at a time, not all at once.
  2. 02Forgetting that for await...of also works on plain, synchronous iterables can confuse debugging of sequential async behavior.
Notes
Useful Cheatsheetsusefulcheatsheets.com
JavaScript Async Iterators
Chapter 25 · Page 199
Advanced

JavaScript Async Iterators

(continued)

What Async Iterators Are

  • Symbol.asyncIterator

    `next()` returns a Promise resolving to `{ value, done }`, not a plain object.

    const asyncIt = {
      [Symbol.asyncIterator]() {
        let i = 0;
        return {
          next: () =>
            Promise.resolve(
              { value: i++, done: i > 3 }
            )
        };
      }
    };
  • Class-based iterable

    A class implements async iteration by returning `this` from `[Symbol.asyncIterator]()`.

    class Logs {
      constructor(lines) {
        this.i = 0;
        this.lines = lines;
      }
      [Symbol.asyncIterator]() { return this; }
      next() {
        const done = this.i >= this.lines.length;
        const value = this.lines[this.i++];
        return Promise.resolve({ value, done });
      }
    }
  • Dual protocols

    An object can implement `Symbol.iterator` and `Symbol.asyncIterator` for two iteration modes.

    const range = {
      [Symbol.iterator]() {
        let i = 0;
        return {
          next: () => ({ value: i++, done: i > 2 })
        };
      },
      [Symbol.asyncIterator]() {
        let i = 0;
        return {
          next: () => Promise.resolve(
            { value: i++, done: i > 2 }
          )
        };
      }
    };
  • Manual next() calls

    Call `next()` directly and `await` each promise to drive iteration by hand.

    async function drain(it) {
      let result = await it.next();
      while (!result.done) {
        console.log(result.value);
        result = await it.next();
      }
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
JavaScript Async Iterators
Chapter 25 · Page 200
Advanced

JavaScript Async Iterators

(continued)

Async Generator Functions

  • async function*

    Declare an async generator by combining `await` and `yield` in one body.

    async function* fetchPages(url) {
      let next = url;
      while (next) {
        const res = await fetch(next);
        const page = await res.json();
        yield page.items;
        next = page.nextUrl;
      }
    }
  • yield pauses

    Each `yield` suspends the generator until the caller requests the next value.

    async function* ticker() {
      console.log('start');
      yield 1;
      console.log('resumed');
      yield 2;
    }
    const t = ticker();
    await t.next(); // logs 'start'
    await t.next(); // logs 'resumed'
  • Returns an iterable

    Calling an async generator returns an async iterable right away; the body waits.

    async function* slow() {
      console.log('running');
      yield 1;
    }
    const gen = slow(); // logs nothing yet
    await gen.next();   // now logs 'running'
  • Error propagation

    An error thrown inside the generator rejects the promise `next()` returns.

    async function* risky() {
      yield 1;
      throw new Error('boom');
    }
    try {
      for await (const v of risky()) {
        console.log(v);
      }
    } catch (e) {
      console.log(e.message); // 'boom'
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
JavaScript Async Iterators
Chapter 25 · Page 201
Advanced

JavaScript Async Iterators

(continued)

The for await...of Loop

  • for await...of

    Consume an async iterable with `for await...of`, awaiting each value automatically.

    async function run() {
      for await (const items of
        fetchPages('/api/items')) {
        console.log(items);
      }
    }
  • Waits per iteration

    The loop body only runs once the currently yielded promise resolves.

    async function* slowNums() {
      yield 1;
      await new Promise(r => setTimeout(r, 100));
      yield 2;
    }
    for await (const n of slowNums()) {
      console.log(Date.now(), n);
    }
  • Scope restriction

    `for await...of` only works inside an async function or a module's top level.

    async function readAll(stream) {
      for await (const chunk of stream) {
        process(chunk);
      }
    }
  • Accepts sync too

    It also accepts plain, synchronous iterables, awaiting each value for consistency.

    for await (const n of [1, 2, 3]) {
      console.log(n);
    }
    // logs: 1 2 3
  • Cleanup on exit

    `break` or `return` inside the loop runs the generator's `finally` block for cleanup.

    async function* withCleanup() {
      try {
        yield 1;
        yield 2;
      } finally {
        console.log('cleanup');
      }
    }
    for await (const n of withCleanup()) {
      if (n === 1) break;
    }
    // logs 'cleanup' after break
Notes
Useful Cheatsheetsusefulcheatsheets.com
JavaScript Async Iterators
Chapter 25 · Page 202
Advanced

JavaScript Async Iterators

(continued)

Consuming Streams and Paginated APIs

  • Wrap the endpoint

    Wrap a paginated endpoint in an async generator so callers never see cursor logic.

    async function* paginate(fetchPage) {
      let cursor = null;
      do {
        const { items, nextCursor } =
          await fetchPage(cursor);
        yield* items;
        cursor = nextCursor;
      } while (cursor);
    }
  • yield* delegation

    `yield*` unpacks an iterable and emits each item, instead of one array.

    async function* asPage() {
      yield [1, 2, 3];
    }
    async function* asItems() {
      yield* [1, 2, 3];
    }
    // asPage yields one array
    // asItems yields 1, then 2, then 3
  • Lazy evaluation

    Pages fetch only when consumed; `break` stops further fetches and keeps memory flat.

    let pagesFetched = 0;
    async function* lazyPages() {
      while (true) {
        pagesFetched++;
        yield pagesFetched;
      }
    }
    for await (const page of lazyPages()) {
      if (page === 2) break;
    }
    console.log(pagesFetched); // 2, not more
  • Native stream support

    Node.js Readable streams implement `Symbol.asyncIterator` natively, so they work here.

    for await (const chunk of
      fs.createReadStream('file.txt')) {
      console.log(chunk.length);
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
JavaScript Async Iterators
Chapter 25 · Page 203
Advanced

JavaScript Async Iterators

(continued)

Async vs Sync Iteration

  • Sync vs async generator

    A sync generator yields values directly; an async one yields promises that resolve.

    function* syncGen() { yield 1; yield 2; }
    async function* asyncGen() {
      yield 1;
      yield 2;
    }
  • Matching loop

    Plain `for...of` cannot read an async iterable; it needs `for await...of` to unwrap values.

    for (const x of asyncGen()) {}
    // TypeError: not a function or its
    // return value is not iterable
    
    for await (const x of asyncGen()) {
      console.log(x); // 1, then 2
    }
  • Spread limitation

    Spread syntax (`...`) only works with sync iterables; it cannot await an async one.

    console.log([...asyncGen()]);
    // TypeError: not a function or its
    // return value is not iterable
  • await alone isn't enough

    Adding `await` inside a plain `function*` is a syntax error; use `async function*`.

    function* broken() {
      yield 1;
      await Promise.resolve(2);
      // SyntaxError: await is only
      // valid in async functions
    }
  • Converting sync to async

    Wrap a sync iterable in an async generator, awaiting each value as it's yielded.

    async function* toAsync(iterable) {
      for (const value of iterable) {
        yield await Promise.resolve(value);
      }
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
JavaScript Async Iterators
Chapter 25 · Page 204
Advanced

JavaScript Async Iterators

(FAQ)

FAQ

Symbol.iterator defines a synchronous iterator whose next() returns {value, done} directly. Symbol.asyncIterator defines an async iterator whose next() returns a Promise that resolves to {value, done}. Use the async version whenever producing a value requires waiting, like a network request.

Combine the async and function* keywords into async function*. Inside it, use await for asynchronous work and yield to emit values. Calling it returns an async iterable you can loop over with for await...of.

Use for await...of when looping over an async iterable, such as an async generator or a stream of paginated results. It automatically awaits each yielded promise before running the loop body. A regular for...of loop cannot await values produced asynchronously.

Yes — for await...of works with any iterable, sync or async, and awaits each value automatically. Given an array of promises, it awaits each one in order before continuing. This makes it useful for processing a fixed list of pending requests sequentially.

An async generator can fetch one page, yield its items, then fetch the next page only when asked. This keeps memory usage low since pages load lazily instead of all at once. Callers just loop with for await...of and never see the pagination logic.

Useful Cheatsheetsusefulcheatsheets.com
JavaScript Async Iterators
Chapter 25 · Page 205
Advanced

JavaScript Async Iterators

(In Practice)
In Practice

Streaming Paginated Search Results

An async generator lazily fetches pages of search results, and for await...of stops as soon as a match is found.

  1. 01fetchResults is an async generator that fetches one page of results at a time.
  2. 02yield* items emits each item individually instead of yielding whole page arrays.
  3. 03for await...of automatically awaits each yielded item before running the loop body.
  4. 04Returning early from the loop stops fetching further pages the caller no longer needs.
async function* fetchResults(query) {
  let cursor = null;
  do {
    const res = await fetch(`/api/search?q=${query}&cursor=${cursor ?? ''}`);
    const { items, nextCursor } = await res.json();
    yield* items;
    cursor = nextCursor;
  } while (cursor);
}

async function findFirstMatch(query, predicate) {
  for await (const item of fetchResults(query)) {
    if (predicate(item)) return item;
  }
  return null;
}

const match = await findFirstMatch('laptop', item => item.price < 500);
console.log(match);
// stops fetching pages as soon as a match is found
Takeaway

Async generators plus for await...of stream results lazily — you only fetch as many pages as you actually need.