Arrays & Iterables
Generators
Master JavaScript generator functions. Learn generator declaration using function*, yielding values, execution suspension, and managing custom iterators.
1. Introduction
Generators are special functions that can be exited and later re-entered, preserving their context (variable bindings) across entries. They provide a powerful way to implement custom iterators and handle asynchronous data streams.
2. Why It Matters
Generators allow you to produce sequences of values lazily (calculating values only when requested). This makes it possible to work with infinite sequences or process massive datasets without consuming a lot of memory.
3. Real-World Analogy
Think of a Token Dispenser at a Fair:
- Standard Function (Pre-packed Bag of Tokens): The machine pre-packages 100 tokens and hands you the bag. You must carry all 100 tokens (high memory consumption), even if you only end up playing two games.
- Generator (Press to Dispense Machine): The machine sits idle. When you press the button (call
next()), it dispenses a single token (yield) and pauses. The machine remembers how many tokens it has left, but does not print any more until you press the button again. You only carry what you need.
4. How It Works
Generators are declared using the asterisk syntax (function*) and use the yield keyword to pause execution and return a value:
5. Architectural Features
- Two-way communication: The
yieldexpression evaluated inside the generator can receive values passed in by the caller vianext(value). - Auto-implementation of Iteration: Generator objects are built-in iterables, meaning they can be looped over directly using
for...ofloops or unpacked using the spread operator.
6. Practical Example
This script demonstrates generating an infinite sequence of Fibonacci numbers using a generator:
7. Common Mistakes
- Trying to reuse a finished generator: Once a generator has completed (returned
done: true), callingnext()on it repeatedly only returns{ value: undefined, done: true }. To run the generator again, you must create a new generator instance.
8. Quick Quiz
Q1: Can arrow functions be declared as generator functions?
A) Yes, using the () => {} syntax
B) No, generators require the function keyword syntax
Answer: B — Arrow functions cannot be used as generators because they do not support the function* declaration syntax.
9. Scenario-Based Challenge
The Paginated API Stream:
An API returns paginated data: /users?page=1. You want to hide page numbers and request states from developers. Design a generator function streamUsers(api) that yields individual user records sequentially, fetching the next page of data automatically behind the scenes only when needed.
10. Debugging Exercise
Explain why this generator call logs undefined, and how to fix it:
function* game() {
const answer = yield 'What is 5 + 5?';
console.log('User answer: ' + answer);
}
const session = game();
session.next(); // returns { value: "What is 5 + 5?", done: false }
session.next(); // logs "User answer: undefined"! Why?
View Solution
Diagnosis: The first next() call starts execution and yields the question string. The second next() call resumes execution, but does not pass an argument, so the yield expression evaluates to undefined.
Fix: Pass the answer value as an argument to the second next() call:
const session = game();
console.log(session.next().value); // "What is 5 + 5?"
session.next(10); // logs "User answer: 10"
11. Interview Questions
🟢 Q1: Explain execution context suspension in generator functions.
Answer: When a generator function yields a value, its stack frame is popped off the Call Stack, suspending execution. However, the JS engine keeps the generator's execution context (including local variable bindings and scope links) alive in the heap. When the caller invokes next() again, the engine pushes the generator context back onto the Call Stack and resumes execution from the exact line where it was paused.
12. Production Considerations
- • Memory Efficiency: Generators are highly efficient for processing massive datasets (like parsing gigabyte-sized log files) because they avoid loading the entire dataset into memory at once, processing it one element at a time instead.