JavaScript Async Iterators

`for await..of` and Streams of Values Over Time

JavaScript Async Iterators

Async iterators yield promises. `for await..of` waits for each one before moving on — perfect for streams, paginated APIs, and event sources.

5 min read Level 3/5 #async-iterators#for-await-of#streams
What you'll learn
  • Use `for await..of` to consume async iterables
  • Write an async generator with `async function*`
  • Model paginated APIs as async streams

A regular iterator gives you values one at a time, synchronously. An async iterator gives you values one at a time, where each value might arrive later — a network page, the next chunk of a file, the next event from a socket. The protocol is the same idea, but .next() returns a Promise<{ value, done }>.

for await..of

You consume an async iterable with for await..of. The loop body waits for each promise to resolve before continuing.

Sequential awaits in a loop script.js
async function delay(ms, value) {
  return new Promise(r => setTimeout(() => r(value), ms));
}

async function run() {
  const tasks = [delay(50, "a"), delay(20, "b"), delay(10, "c")];
  for await (const v of tasks) {
    console.log(v);   // "a", "b", "c" — in array order, not finish order
  }
}
run();
▶ Preview: console

Async Generators

Mark a generator async function* and yield becomes await-friendly.

An async generator script.js
async function* trickle() {
  for (let i = 1; i <= 3; i++) {
    await new Promise(r => setTimeout(r, 100));
    yield i;
  }
}

async function run() {
  for await (const n of trickle()) {
    console.log(n);   // 1 ... 2 ... 3  (100ms apart)
  }
}
run();
▶ Preview: console

Paginated APIs as Streams

The async iterator pattern shines when you don’t know in advance how many pages there are.

async function* paginate(url) {
  let next = url;
  while (next) {
    const res = await fetch(next);
    const page = await res.json();
    for (const item of page.items) yield item;
    next = page.nextUrl;   // null when no more pages
  }
}

// caller never sees the pagination — just a stream of items
for await (const item of paginate("/api/users")) {
  console.log(item.name);
}

The consumer reads like a normal loop. The generator handles the pagination internally.

Sync vs Async — Side by Side

ConceptSyncAsync
Iterator hook[Symbol.iterator]()[Symbol.asyncIterator]()
.next() returns{ value, done }Promise<{ value, done }>
Consumed withfor (const x of it)for await (const x of it)
Producer keywordfunction* / yieldasync function* / yield (can await inside)

Up Next

That wraps async, errors, and modules. Time to leave the language core and look at how JavaScript talks to the page — the DOM.

JavaScript DOM Intro →