JavaScript Generators: Complete Guide with Examples
What are JavaScript Generator Functions?
Generator functions in JavaScript are special functions that can be paused and resumed, allowing them to yield multiple values over time. Unlike regular functions that return a single value, generators can produce a sequence of values, making them extremely useful for handling iterative algorithms, managing asynchronous operations, and working with large data sets.
Key Point: Generator functions are defined using the function* syntax and use the yield keyword to pause execution and return values.
Generator Function Syntax
The syntax for creating generator functions involves using the function* declaration and the yield operator:
function* myGenerator() {
yield 'First value';
yield 'Second value';
return 'Final value';
}
All of these syntax variations are valid:
| Syntax | Example | Validity |
|---|---|---|
| function* generator() | function* myGenerator() { } |
✅ Valid (Most Common) |
| function *generator() | function *myGenerator() { } |
✅ Valid |
| function*generator() | function*myGenerator() { } |
✅ Valid |
How Generator Functions Work
When you call a generator function, it doesn’t execute its code immediately. Instead, it returns a special object called a Generator object. This object implements the iterator protocol, meaning you can call next() on it to execute code until the next yield expression.
Basic Generator Example
function* simpleGenerator() {
yield 'Hello';
yield 'World';
yield '!';
}
const gen = simpleGenerator();
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 }
Generator Object Methods
The generator object returned by a generator function has three main methods:
| Method | Description | Example |
|---|---|---|
| next() | Resumes execution until the next yield expression | gen.next() |
| return() | Returns the given value and terminates the generator | gen.return('value') |
| throw() | Throws an error into the generator at the point where it was paused | gen.throw(new Error()) |
Using yield* for Delegation
The yield* expression is used to delegate to another generator or iterable object. This is useful for calling one generator from another or implementing recursive generator functions.
Generator Delegation Example
function* generatorOne() {
yield 'One';
yield 'Two';
}
function* generatorTwo() {
yield 'Start';
yield* generatorOne(); // Delegating to generatorOne
yield 'End';
}
const gen = generatorTwo();
for (const value of gen) {
console.log(value);
}
// Output: 'Start', 'One', 'Two', 'End'
Generators and Iterators
Since generators are iterable, they work seamlessly with loops and the spread operator. This makes them perfect for creating custom iterable objects.
Custom Iterable with Generator
const customIterable = {
start: 1,
end: 5,
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
};
console.log([...customIterable]); // [1, 2, 3, 4, 5]
Practical Use Cases for Generators
Generators are particularly useful for:
- Implementing custom iterators for complex data structures
- Handling asynchronous operations in a synchronous-looking way
- Generating infinite sequences (like Fibonacci series)
- Managing state machines
- Lazy evaluation of data
Infinite Sequence Example
function* infiniteSequence() {
let num = 0;
while (true) {
yield num++;
}
}
const numbers = infiniteSequence();
console.log(numbers.next().value); // 0
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 2
// This can continue indefinitely