Generators in JavaScript
A generator is a special type of function that can pause and resume its execution.
- Declared using the
function*
syntax. - Uses
yield
to return values one at a time. - Automatically produces an iterator when called.
Syntax :
function* generatorFunction() {
yield value1;
yield value2;
return finalValue; // optional
}
yield
: Pauses the generator and returns a value.return
: Ends the generator; value is returned anddone
becomestrue
.
Basic Example
function* numbers() {
yield 1;
yield 2;
yield 3;
}
const gen = numbers();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
Characteristics of Generators
- Iterator Protocol: Generators implement the iterator protocol automatically.
- Lazy Evaluation: Values are generated on-demand, saving memory for large sequences.
- Stateful: Generators maintain their execution context between
yield
calls. - Single-use: Once finished, a generator cannot be restarted; you need a new generator instance.
Iterating Generators
You can iterate over a generator using:
a. for...of
Loop
function* letters() {
yield 'A';
yield 'B';
yield 'C';
}
for (const letter of letters()) {
console.log(letter);
}
// Output: A B C
b. while
Loop with .next()
const gen = letters();
let result = gen.next();
while (!result.done) {
console.log(result.value);
result = gen.next();
}
// Output: A B C
Passing Values to Generators
You can pass values back into a generator using .next(value)
:
function* counter() {
const start = yield "Enter start value:";
yield start + 1;
yield start + 2;
}
const gen = counter();
console.log(gen.next()); // { value: "Enter start value:", done: false }
console.log(gen.next(5)); // { value: 6, done: false }
console.log(gen.next()); // { value: 7, done: false }
Error Handling in Generators
Generators can handle errors using .throw()
:
function* genFunc() {
try {
yield 1;
} catch (err) {
console.log('Caught:', err);
}
}
const gen = genFunc();
console.log(gen.next()); // { value: 1, done: false }
gen.throw(new Error("Oops")); // Caught: Error: Oops
Practical Example: Infinite Sequence
Generators are perfect for lazy sequences:
function* infiniteNumbers() {
let i = 0;
while (true) {
yield i++;
}
}
const gen = infiniteNumbers();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
Use Cases
- Lazy Evaluation: Generate large or infinite sequences without memory overhead.
- Custom Iteration: Simplify iteration logic for complex data structures.
- Asynchronous Programming: Combine with
async
andawait
for advanced flow control. - State Machines: Maintain execution context across multiple steps.