Photo by chansu shin on Unsplash

On this page

This chapter follows Generators in JavaScript.

Synchronous iterators assume each next() can return immediately. Async iterators return a Promise for each step, so producers can wait on I/O, timers, or other async work between values.

Async iterator protocol

An async iterator exposes next() returning Promise<{ value, done }>. An async iterable implements [Symbol.asyncIterator]() and returns a fresh async iterator.

You can also implement the protocol by hand:

const asyncIterable = {
async *[Symbol.asyncIterator]() {
yield 1;
yield 2;
},
};

for await..of

for await..of loops over async iterables (and sync iterables in async functions). Each iteration awaits the next result:

async function logAll(source) {
for await (const value of source) {
console.log(value);
}
}
await logAll(asyncIterable);

Use for await..of inside async functions or at the top level in modules that support top-level await. Plain for..of on an async iterable does not await promises between steps.

Async generator functions

async function* combines generators with await. The returned object is both an async iterator and an async iterable:

async function* readLines(urls) {
for (const url of urls) {
const res = await fetch(url);
const text = await res.text();
for (const line of text.split("\n")) {
if (line.trim()) yield line;
}
}
}
for await (const line of readLines(["/api/a", "/api/b"])) {
console.log(line);
}

Rules mirror sync generators: yield produces a value, yield* delegates to another iterable, and return() / throw() on the iterator can finish early.

When to choose async iteration

Reach for async iterables when:

  • Values arrive over time and each step may await
  • You want lazy consumption (process one page or chunk before fetching the next)
  • A library exposes Symbol.asyncIterator (Node readable streams in modern modes, some database drivers)

For a single future value, a Promise is simpler. For push streams with many subscribers and operators, see Observables in JavaScript.

Error handling and cleanup

If for await..of throws or breaks out of the loop, the runtime calls iterator.return() when present—same idea as sync for..of. Async generators run finally blocks when iteration ends.

Wrap the loop in try/catch when the producer can reject (network failures, parser errors):

async function consume(source) {
try {
for await (const chunk of source) {
process(chunk);
}
} catch (err) {
console.error("Iteration failed", err);
}
}

For typing async generators in TypeScript, see Async generator in TypeScript.

Continue with Iterator helpers.

Search articles

Type a word to search published articles.

Open the search page