JavaScript Iterators
Understand iterables, the iteration protocol, generators, and async iteration with for await...of.
TL;DR
- Expose a
nextmethod returningvalueanddoneproperties. - Implement
Symbol.iteratorto make custom objects natively iterable. - Use generator functions to construct custom iterables efficiently.
What Iterators Are
Iterator definitionProvides a next method returning value and done flags.
const arr = [10, 20];
const it = arr[Symbol.iterator]();
it.next(); // { value: 10, done: false }Iteration stateTracks progress dynamically, returning done: true when complete.
it.next(); // { value: 20, done: false }
it.next(); // { value: undefined, done: true }Underlying supportPowers loops, spreads, and destructuring operations implicitly.
const [x, y] = [10, 20]; // uses iteratorIteration Protocol and Built-in Iterables
Custom iterablesImplements Symbol.iterator to make objects work with for-of.
const obj = {
data: [1, 2, 3],
[Symbol.iterator]() {
let i = 0;
return { next: () => ({ value: this.data[i], done: i++ >= this.data.length }) };
}
};
for (const n of obj) console.log(n);Required structuresDemands the standard iterator method shape to match engine interfaces.
// Iterator returns: { next() { ... } }Standard collectionsProvides native iteration for strings, arrays, sets, and maps.
for (const char of "Hi!") console.log(char);
for (const val of new Set([1, 2])) console.log(val);Map entriesIterates over key-value pairs using array destructuring syntax.
const map = new Map([['a', 1]]);
for (const [k, v] of map) console.log(k, v);DOM NodeListsSupports for-of iteration on elements retrieved from DOM queries.
const divs = document.querySelectorAll("div");
for (const div of divs) console.log(div);Generators
Generator functionsPauses function execution using the yield keyword.
function* greet() {
const name = yield "name?";
yield `Hello, ${name}!`;
}
const g = greet();
g.next().value; // "name?"
g.next("Ada").value; // "Hello, Ada!"Generator delegationDelegates execution to nested iterables using the yield* operator.
function* combine() {
yield* [1, 2];
yield* ['a', 'b'];
}
console.log([...combine()]); // [1, 2, 'a', 'b']Async Generator Functions
async function*Yield awaited values one at a time, over time.
async function* pages(url) {
for (let n = url; n; ) {
const p = await (await fetch(n)).json();
yield p.items;
n = p.nextUrl;
}
}yield pausesEach yield pauses until the next next() call.
async function* ticker() {
yield 1;
yield 2;
}
const t = ticker();
await t.next(); // { value: 1, done: false }Error propagationA thrown error surfaces where you consume it.
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); // 1, then boom
}The for await...of Loop
for await...ofConsume 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 iterationThe 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 restrictionfor 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);
}
}Tips
- Use generator functions to implement the iteration contract automatically without manual state tracking.
- Delegate execution to nested iterables using
yield*to simplify generator loop declarations.
Warnings
- Remember that exhausted iterators cannot be reused without obtaining a fresh iterator instance.
- Convert plain objects using
Object.entries()before attempting loop iteration over them.
In Practice
Wraps a paginated server endpoint inside a custom async iterator for streaming record sets.
- Set up local trackers for page counts and finished statuses.
- Expose the iterator protocol handler method on the container.
- Define the async next method structure to request records.
- Fetch server data records and adjust page markers recursively.
- Return the received records list or done flags.
async function* pages(fetcher) {
let page = 1;
while (true) {
const { items, hasMore } = await fetcher(page++);
yield* items;
if (!hasMore) return;
}
}FAQ
An iterable is an object defining a Symbol.iterator method. An iterator is the returned object containing a next() method. Arrays are iterables, while array.values() returns an iterator.
Yes, define a Symbol.iterator method returning a next() method on your object. That method must return a {value, done} structure. The object then supports for...of loops.
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.