JavaScript Async Iterators — for await...of and Async Generators | TryJS

Learn JavaScript async iterators and generators with runnable examples. Stream paginated APIs, process incoming data one chunk at a time, and use for await...of.

Overview

Async iterators let you consume asynchronous sequences one value at a time with clean syntax. for await...of is the async cousin of for...of — it awaits each next() call, pausing the loop until the next value arrives. Combined with async generators (async function*), they become the best tool for streaming paginated APIs, reading files line by line, and processing incoming websocket messages without buffering everything into memory.

How It Works

Async generator with async function*

An async generator yields values asynchronously. Each yield returns a promise to the consumer; the next yield waits for the consumer to call next() again. The generator body can await anything between yields.

for await...of consumes async iterables

for await (const x of asyncIterable) calls next(), awaits the result, unwraps .value, and runs the body. When next() returns { done: true }, the loop ends. Any rejection jumps to try/catch just like a regular await.

Pagination is the canonical use case

Fetch page 1, yield each item, fetch page 2, yield each item, repeat until no more pages. The consumer sees a flat stream; the generator handles pagination internally.

Early exit with break

break inside for await...of calls the iterator's return() method, giving the generator a chance to clean up. This is how you stop streaming when you've found what you need.

Common Mistakes

When to Use It

Use async iterators for streaming data with unknown or large size: paginated APIs, line-by-line file reading, incoming messages over a socket, chunked HTTP responses. Use a plain array + Promise.all when the total count is small and known.

Runnable Example

async function* paginate(pageSize) {
  for (let page = 1; page <= 3; page++) {
    // Simulate API call
    await new Promise(r => setTimeout(r, 50));
    const items = Array.from(
      { length: pageSize },
      (_, i) => `page${page}-item${i + 1}`,
    );
    console.log(`fetched page ${page}`);
    for (const item of items) yield item;
  }
}

(async () => {
  // Stream every item across all pages
  for await (const item of paginate(3)) {
    console.log("got:", item);
  }

  // Early exit
  console.log("--- with break ---");
  for await (const item of paginate(3)) {
    console.log("scan:", item);
    if (item === "page2-item2") break;
  }
})();

Open this example in the TryJS playground to edit and run the code instantly in your browser — no signup needed.

Frequently Asked Questions

What is an async iterator in JavaScript?

An async iterator is an object whose next() method returns a promise for { value, done }. You consume it with for await...of, which awaits each step. Async generators (async function*) are the easiest way to build one.

When should I use for await...of instead of Promise.all?

Use for await...of when values arrive over time (streaming, pagination, sockets) or when the total count is unknown. Use Promise.all when you have a fixed list of independent promises and you want them all to run concurrently.

Can I break out of a for await...of loop early?

Yes — break works and also calls the iterator's return() method, which gives an async generator a chance to clean up (close connections, stop fetching). This makes 'first match wins' streaming searches clean and safe.

Does for await...of run in parallel?

No — it runs serially by design. Each iteration waits for the previous one's await to settle. If you want parallelism over a stream, collect items in batches and Promise.all the batches.