Generators in JavaScript are a powerful feature introduced in ECMAScript 6 (ES6) that allow you to define iterative algorithms by pausing and resuming execution at defined points. They provide a flexible way to control iteration and asynchronous operations.
How Generators Work
Generators are defined using function* syntax (note the asterisk). They use the yield
keyword to pause execution and return values sequentially. When called, a generator function returns an iterator object that can be used to control the iteration.
Let's explore some practical examples to understand how generators are used:
Example 1: Fibonacci Sequence Generator
function* fibonacciGenerator() {
let prev = 0;
let curr = 1;
yield prev;
yield curr;
while (true) {
let next = prev + curr;
yield next;
prev = curr;
curr = next;
}
}
// Usage
const fibonacciSequence = fibonacciGenerator();
for (let i = 0; i < 10; i++) {
console.log(fibonacciSequence.next().value); // Outputs: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
}
In this example, fibonacciGenerator
generates an infinite sequence of Fibonacci numbers, pausing after each yield
statement until next()
is called again.
Example 2: Inorder Traversal of a Binary Tree
class Node {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
function* inorderTraversal(node) {
if (node !== null) {
yield* inorderTraversal(node.left); // Traverse left subtree
yield node.value; // Yield current node's value
yield* inorderTraversal(node.right); // Traverse right subtree
}
}
// Usage
const rootNode = new Node(10);
rootNode.left = new Node(5);
rootNode.right = new Node(15);
// (Set up the rest of the tree as in the example)
const iterator = inorderTraversal(rootNode);
const result = [];
for (let value of iterator) {
result.push(value);
}
console.log(result); // Outputs: [3, 5, 7, 10, 15, 18]
This generator function performs an inorder traversal of a binary tree, yielding values in the correct order.
Example 3: Custom Iterator Using Generators
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
// Usage
const range = new Range(1, 5);
for (let num of range) {
console.log(num); // Outputs: 1, 2, 3, 4, 5
}
Here, the Range
class uses a generator to create an iterable range of numbers from start
to end
.
Example 4: Asynchronous Control Flow
function fetchDataFromAPI(endpoint) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { endpoint, results: [1, 2, 3, 4, 5] };
resolve(data);
}, Math.random() * 1000);
});
}
function* fetchAndProcessData() {
try {
const data1 = yield fetchDataFromAPI('/api/data1');
console.log('Processed data1:', data1);
const data2 = yield fetchDataFromAPI('/api/data2');
console.log('Processed data2:', data2);
const data3 = yield fetchDataFromAPI('/api/data3');
console.log('Processed data3:', data3);
console.log('All data processed!');
} catch (error) {
console.error('Error:', error);
}
}
function runGenerator(generator) {
const iterator = generator();
function iterate(iteration) {
if (iteration.done) {
return iteration.value;
}
const promise = iteration.value;
return promise.then((value) => iterate(iterator.next(value)))
.catch((error) => iterator.throw(error));
}
return iterate(iterator.next());
}
// Usage
runGenerator(fetchAndProcessData);
This example demonstrates using a generator to manage asynchronous operations sequentially, yielding promises and processing their results.
Conclusion
Generators offer a flexible way to control iteration and manage asynchronous flows in JavaScript. By pausing execution with yield
, they allow complex tasks to be handled in a more readable and sequential manner. Understanding generators enhances your ability to write efficient and expressive JavaScript code.
Top comments (0)