Generators are one of the most underused features in JavaScript. At Google, we use them for lazy evaluation, pagination, and complex async flows.
The Iterator Protocol
Any object with a next() method returning { value, done } is an iterator. Any object with [Symbol.iterator]() returning an iterator is iterable.
const range = {
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) yield i;
},
start: 1,
end: 5
};
console.log([...range]); // [1, 2, 3, 4, 5]Generator Functions
Generators are functions that can pause and resume. The yield keyword pauses execution and returns a value.
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
fib.next().value; // 0
fib.next().value; // 1
fib.next().value; // 1Practical Use Cases
1. Lazy Pagination
async function* fetchPages(url) {
let page = 1;
while (true) {
const res = await fetch(`${url}?page=${page}`);
const data = await res.json();
if (data.length === 0) return;
yield data;
page++;
}
}
for await (const page of fetchPages("/api/users")) {
renderUsers(page);
}2. Cancellable Async Flows
Unlike Promises, generators can be cancelled by simply not calling next() again. This is the foundation of libraries like Redux-Saga.
3. Tree Traversal
function* traverse(node) {
yield node.value;
for (const child of node.children) {
yield* traverse(child);
}
}Generator + Async = Power
Async generators (async function*) combine the best of both worlds — lazy evaluation with async data sources. This is how Node.js streams work under the hood.