Generators
A generator is a function that can pause and resume. You write it like ordinary code, but you use yield to hand a value back to the caller and stop until the caller asks for more. Under the hood, calling a generator produces an iterator — so anything that consumes iterators (for...of, spread, destructuring) consumes generators for free.
function* and yield
A generator function is declared with function*. Calling it doesn't run the body — it returns a generator object, which is both an iterator and an iterable.
function* abc() {
yield "a";
yield "b";
yield "c";
}
const g = abc();
console.log(g.next()); // { value: 'a', done: false }
console.log(g.next()); // { value: 'b', done: false }
console.log(g.next()); // { value: 'c', done: false }
console.log(g.next()); // { value: undefined, done: true }{ value: 'a', done: false }
{ value: 'b', done: false }
{ value: 'c', done: false }
{ value: undefined, done: true }Each yield is a pause point. The function suspends after yielding and only continues when next() is called again. When the function returns (or runs off the end), done becomes true.
Using a generator with for...of
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
for (const n of range(0, 5)) console.log(n);
console.log([...range(0, 10, 2)]);0 1 2 3 4 [ 0, 2, 4, 6, 8 ]
Compare to the manual iterator on the Iterators page — this version is about a third of the code, and you can read it top to bottom.
Lazy evaluation
The body of a generator only runs as far as the next yield. That makes generators perfect for sequences where computing every value is expensive or impossible.
Infinite sequence of natural numbers
function* naturals() {
let i = 1;
while (true) yield i++;
}
const g = naturals();
console.log(g.next().value); // 1
console.log(g.next().value); // 2
console.log(g.next().value); // 3Yes, the loop body says while (true) — but it only runs one tick per next(), so the function never actually loops forever unless you keep asking.
Taking the first n values
A common pattern: combine an infinite generator with a "take" helper.
function* take(iterable, n) {
let i = 0;
for (const value of iterable) {
if (i++ >= n) return;
yield value;
}
}
console.log([...take(naturals(), 5)]); // [ 1, 2, 3, 4, 5 ]return inside a generator marks it done — anything after won't run.
yield*: delegate to another iterable
Inside a generator, yield* yields all the values of another iterable in order. It's the easy way to compose generators or flatten nested sequences.
function* letters() { yield "a"; yield "b"; yield "c"; }
function* digits() { yield 1; yield 2; yield 3; }
function* both() {
yield* letters();
yield* digits();
}
console.log([...both()]);[ 'a', 'b', 'c', 1, 2, 3 ]
Two-way comms with next(value)
This is where generators get unusual. yield is an expression — and the value passed to the next next(value) call becomes the result of the paused yield. That gives you two-way communication between caller and generator.
function* dialogue() {
const name = yield "What is your name?";
const lang = yield `Hi ${name}. Favourite language?`;
return `${name} likes ${lang}.`;
}
const d = dialogue();
console.log(d.next().value); // "What is your name?"
console.log(d.next("Ada").value); // "Hi Ada. Favourite language?"
console.log(d.next("Lisp").value); // "Ada likes Lisp."The first next() doesn't pass anything — there's no yield waiting yet. Each subsequent call's argument feeds the previously paused yield.
throw() and return()
Generators expose two more methods besides next:
gen.throw(err)— resume the generator by throwingerrat the pausedyield. Useful for surfacing errors from a runner.gen.return(value)— finish the generator immediately, as if areturn valuehad run at the current pause point. Cleanup paths infor...ofuse this.
function* safe() {
try {
yield 1;
yield 2;
} catch (err) {
console.log("caught:", err.message);
}
}
const g = safe();
console.log(g.next().value); // 1
console.log(g.throw(new Error("nope")));
// caught: nope
// { value: undefined, done: true }Practical recipes
A few patterns that come up often:
ID generator
function* idGenerator(prefix = "id") {
let i = 0;
while (true) yield `${prefix}-${++i}`;
}
const ids = idGenerator("user");
console.log(ids.next().value); // user-1
console.log(ids.next().value); // user-2Walking a tree lazily
function* walk(node) {
yield node;
for (const child of node.children ?? []) {
yield* walk(child);
}
}
for (const node of walk(root)) {
if (node.id === "target") return node; // stops the generator early
}