JavaScript Generators
Generators are special functions that can pause and resume their execution. They are declared with function* syntax and use the yield keyword to produce values on demand. Generators are both iterators and iterables.
Generator Basics: function* and yield
A generator function is defined with function* (or function *). When called, it returns a generator object without executing the body. Each call to next() executes until the next yield statement.
function* greetings() {
yield "Hello";
yield "World";
yield "!";
}
const gen = greetings();
// Each next() runs until the next yield
console.log(gen.next()); // { value: 'Hello', done: false }
console.log(gen.next()); // { value: 'World', done: false }
console.log(gen.next()); // { value: '!', done: false }
console.log(gen.next()); // { value: undefined, done: true }
// Generators are iterable
for (const word of greetings()) {
console.log("for...of:", word);
}
// Spread into array
console.log("Spread:", [...greetings()]);yield and next() Communication
Generators support two-way communication. You can send values INTO a generator by passing an argument to next(). The sent value becomes the result of the yield expression.
function* calculator() {
const a = yield "Enter first number:";
const b = yield "Enter second number:";
yield `Sum: ${a + b}`;
}
const calc = calculator();
console.log(calc.next()); // { value: 'Enter first number:', done: false }
console.log(calc.next(10)); // { value: 'Enter second number:', done: false }
console.log(calc.next(20)); // { value: 'Sum: 30', done: false }
console.log(calc.next()); // { value: undefined, done: true }
// Accumulator generator
function* accumulator() {
let total = 0;
while (true) {
const value = yield total;
total += value;
}
}
const acc = accumulator();
console.log(acc.next()); // { value: 0, done: false }
console.log(acc.next(5)); // { value: 5, done: false }
console.log(acc.next(10)); // { value: 15, done: false }
console.log(acc.next(3)); // { value: 18, done: false }return() and throw()
Generators have return() to end the generator early and throw() to throw an error inside the generator at the point of the current yield.
function* counter() {
let i = 0;
while (true) {
try {
yield i++;
} catch (e) {
console.log("Caught:", e.message);
}
}
}
// return() - ends the generator
const gen1 = counter();
console.log(gen1.next());
console.log(gen1.next());
console.log(gen1.return(99)); // { value: 99, done: true }
console.log(gen1.next()); // { value: undefined, done: true }
// throw() - throws error at yield
const gen2 = counter();
console.log(gen2.next());
console.log(gen2.throw(new Error("Oops")));
console.log(gen2.next());Generators as Iterators
Since generators implement both the iterator and iterable protocols, they are the easiest way to create custom iterables.
// Range generator
function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
console.log("Range 1-5:", [...range(1, 5)]);
console.log("Range 0-10 by 2:", [...range(0, 10, 2)]);
// Make an object iterable with a generator
const team = {
members: ["Alice", "Bob", "Charlie"],
*[Symbol.iterator]() {
for (const member of this.members) {
yield member;
}
}
};
for (const member of team) {
console.log("Member:", member);
}
console.log("Spread team:", [...team]);Lazy Evaluation and Infinite Sequences
Generators compute values lazily -- they only produce the next value when asked. This makes them perfect for infinite sequences and processing large datasets without loading everything into memory.
// Infinite Fibonacci
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// Take first N values from any generator
function take(gen, n) {
const result = [];
for (const val of gen) {
result.push(val);
if (result.length >= n) break;
}
return result;
}
console.log("First 10 Fib:", take(fibonacci(), 10));
// Infinite IDs
function* idGenerator(prefix = "id") {
let n = 1;
while (true) {
yield `${prefix}_${n++}`;
}
}
const ids = idGenerator("user");
console.log(ids.next().value);
console.log(ids.next().value);
console.log(ids.next().value);
// Compose generators with yield*
function* concat(...generators) {
for (const gen of generators) {
yield* gen;
}
}
console.log([...concat(range(1, 3), range(10, 12))]);| Feature | Description |
|---|---|
| function* | Declares a generator function |
| yield | Pauses and emits a value |
| yield* | Delegates to another iterable/generator |
| next() | Resumes execution, optionally sends value |
| return() | Ends generator, returns given value |
| throw() | Throws error at current yield point |