JavaScript generators are special functions that can be paused and resumed during their execution, allowing the producer of the generator to control the pace at which data is generated and consumed. They use the function*
syntax and the yield
keyword to pause ⏸️ the execution and yield values to the calling code.
Synchronous and Asynchronous Behavior
Generators can be used both synchronously and asynchronously, providing a flexible toolset for handling different types of operations.
- 🔄 Synchronous Operations:
function* syncGenerator() {
yield 'Step 1';
yield 'Step 2';
yield 'Step 3';
}
- ⏱️ Asynchronous Operations
function fetchData(url) {
return fetch(url).then(response => response.json());
}
async function* asyncGenerator() {
yield await fetchData('url1');
yield await fetchData('url2');
yield await fetchData('url3');
}
📞 Bidirectional Communication
Generators allow bidirectional communication between the caller and the generator, making them a powerful tool for cooperative multitasking.
function* userPrompt() {
const response = yield 'Please enter your name:';
yield `Hello, ${response}! How can I assist you today?`;
}
const prompt = userPrompt();
console.log(prompt.next().value); // Output: Please enter your name:
console.log(prompt.next('Alice').value); // Output: Hello, Alice! How can I assist you today?
❗ Error Handling
Generators provide elegant error handling through the throw
method. This allows errors to be thrown inside the generator and caught outside it.
function* errorGenerator() {
try {
yield 'Step 1';
throw new Error('Something went wrong');
} catch (error) {
yield `Caught an error: ${error.message}`;
}
}
const iterator = errorGenerator();
console.log(iterator.next().value); // Output: Step 1
console.log(iterator.next().value); // Output: Caught an error: Something went wrong
Design Patterns with Generators
Generators have given rise to several design patterns that simplify complex asynchronous code.
- 🔁 Generator as Iterator
const iterableObject = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
};
- ⚙️ Generator as Control Flow
async function* stepByStepAsync() {
yield asyncOperation1();
yield asyncOperation2();
yield asyncOperation3();
}
Advantages of Generators
- 💾 Efficient Memory Usage: Generators produce values on-the-fly, which can significantly reduce memory usage when dealing with large datasets.
// Generating an Infinite Sequence of Numbers
function* infiniteNumbers() {
let i = 0;
while (true) {
yield i++;
}
}
- ⏩ Lazy Evaluation: Values are generated only when requested, leading to better performance and reduced overhead.
// Lazily Generating Fibonacci Numbers
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
Top comments (0)