`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.
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.
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(); Async Generators
Mark a generator async function* and yield becomes await-friendly.
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(); 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
| Concept | Sync | Async |
|---|---|---|
| Iterator hook | [Symbol.iterator]() | [Symbol.asyncIterator]() |
.next() returns | { value, done } | Promise<{ value, done }> |
| Consumed with | for (const x of it) | for await (const x of it) |
| Producer keyword | function* / yield | async 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 →