Generators and iterators are the protocol that powers for...of, spread ([...x]), and destructuring for any object โ including your own custom types.
The iterator protocol
An iterator is an object with a next() method that returns { value, done }. An iterable is any object with [Symbol.iterator]() that returns an iterator. Built-ins like Array, String, Map, Set, and NodeList implement this already.
const arr = [10, 20, 30];
const iter = arr[Symbol.iterator](); // get the iterator
console.log(iter.next()); // { value: 10, done: false }
console.log(iter.next()); // { value: 20, done: false }
console.log(iter.next()); // { value: 30, done: false }
console.log(iter.next()); // { value: undefined, done: true }
Make any object iterable by adding [Symbol.iterator]:
const range = {
from: 1, to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { value: undefined, done: true };
}
};
}
};
console.log([...range]); // [1, 2, 3, 4, 5]
for (const n of range) console.log(n); // 1 2 3 4 5
Generator functions
A generator function (function*) returns a generator object, which is both an iterator and an iterable. yield pauses execution and produces a value; the function resumes from that point on the next next() call.
console.log. Async logs (setTimeout/Promise) appear in real execution order.Infinite sequences โ the killer use case
Generators can produce values lazily on demand โ no array is ever fully materialized:
console.log. Async logs (setTimeout/Promise) appear in real execution order.yield* delegation
yield* delegates to another iterable, flattening it:
function* concat(...arrays) {
for (const arr of arrays) yield* arr; // yield each item from arr
}
console.log([...concat([1,2], [3,4], [5])]); // [1,2,3,4,5]
Two-way communication โ next(value)
Generators can receive values back via next(value). The value becomes the result of the yield expression:
function* adder() {
let sum = 0;
while (true) {
const n = yield sum; // yield current sum, receive next number
sum += n ?? 0;
}
}
const g = adder();
g.next(); // start (first yield)
g.next(10); // sum = 10
g.next(5); // sum = 15
console.log(g.next(3).value); // 18
Async generators & for awaitโฆof
Async generators yield promises asynchronously โ perfect for paginated APIs:
async function* paginate(url) {
let page = 1;
while (true) {
const res = await fetch(`${url}?page=${page}`);
const data = await res.json();
if (!data.items.length) return;
yield data.items;
page++;
}
}
// Consume lazily
for await (const items of paginate('/api/posts')) {
render(items);
if (shouldStop()) break; // stop at any point โ no over-fetching
}
next() returning { value, done }. An iterable has [Symbol.iterator]() returning an iterator โ this is what powers for...of and spread. Generator functions (function*) produce iterators automatically: yield suspends execution and produces a value, and the function resumes on the next next() call. Generators shine for lazy sequences, infinite streams, and custom iteration. Async generators add await and work with for await...of for paginated or streaming data.โ