If you understand standard JavaScript functions, you know the "Run-to-Completion" rule. Once a normal function starts running, nothing can stop it until it hits a return statement or finishes the code block. It’s like a rollercoaster: once the harness locks, you are on the ride until the end.
Generators break this rule.
Generators are functions that can pause in the middle of execution, let other code run, and then resume exactly where they left off, remembering all their variables.
They turn your function from a Rollercoaster into a Netflix series: you can watch 10 minutes, pause it, go make a sandwich, and come back to the exact same second you left.
1. The Syntax: Asterisks and Yields
To create a generator, you use two special pieces of syntax:
-
function*: The asterisk tells JS "This is a generator." -
yield: This is the "Pause" button.
function* myGenerator() {
console.log("1. Starting...");
yield "First Pause"; // <--- Pauses here and spits out "First Pause"
console.log("2. Resuming...");
yield "Second Pause"; // <--- Pauses here and spits out "Second Pause"
console.log("3. Finishing!");
return "Done";
}
2. The Mechanics: It Doesn't Run Immediately!
This is the part that confuses everyone. When you call a generator function, it does not run the code.
Instead, it returns a Generator Object (an iterator). This object is like a remote control for the function.
const gen = myGenerator(); // No console logs happen here!
To run the code, you have to press "Play" on the remote control. In JavaScript, the "Play" button is .next().
console.log(gen.next());
// Output:
// "1. Starting..."
// { value: "First Pause", done: false }
Understanding the Result
When you call .next(), you get an object back:
-
value: Whatever was to the right of theyieldkeyword. -
done:falsemeans "Paused, more code to come."truemeans "Finished."
Let's finish the function:
console.log(gen.next());
// Output:
// "2. Resuming..."
// { value: "Second Pause", done: false }
console.log(gen.next());
// Output:
// "3. Finishing!"
// { value: "Done", done: true } <--- We hit the 'return', so done is true.
3. Two-Way Communication: Passing Data IN
Most people know yield sends data out. But did you know you can send data in when you restart the function?
Think of yield as a portal.
- The generator throws a value out.
- The generator pauses and waits.
- When you call
.next(data), thatdatatravels back through the portal and becomes the result of theyieldexpression.
function* chattyGenerator() {
const name = yield "What is your name?"; // Pauses here, sends question out
const age = yield "How old are you?"; // Pauses here, waits for input
return `Hello ${name}, you are ${age}!`;
}
const gen = chattyGenerator();
// 1. Start it. It runs until the first yield.
console.log(gen.next().value);
// Output: "What is your name?"
// 2. Resume it, passing data IN.
// This string "John" replaces the 'yield' expression above.
console.log(gen.next("John").value);
// Output: "How old are you?"
// 3. Resume again.
console.log(gen.next(30).value);
// Output: "Hello John, you are 30!"
4. The Infinite Stream (Memory Efficiency)
This is a real-world use case. If you wanted a list of unique IDs, a standard function would need to generate them all and store them in an array. That takes memory.
A Generator creates them Lazy (on demand). It only calculates the next one when you ask for it. You can write an infinite loop that doesn't crash the browser!
function* idGenerator() {
let id = 1;
while (true) { // Infinite loop!
yield id;
id++;
}
}
const ids = idGenerator();
console.log(ids.next().value); // 1
console.log(ids.next().value); // 2
console.log(ids.next().value); // 3
// It pauses at the 'yield' every time.
5. Generator Delegation (yield*)
Sometimes you want one generator to call another generator. You use yield* (yield star). Think of this as the main DJ handing the aux cord to a guest DJ.
function* guestDJ() {
yield "Song A";
yield "Song B";
}
function* mainDJ() {
yield "Intro";
yield* guestDJ(); // Hand over control to guestDJ until it finishes
yield "Outro";
}
const playlist = mainDJ();
// Loop over the generator (yes, you can do this!)
for (const song of playlist) {
console.log(song);
}
// Output:
// Intro
// Song A
// Song B
// Outro
6. Generators & Async (The Precursor to Async/Await)
Before async/await existed, libraries like Redux Saga or co used generators to handle async code. It helps to understand this to see how JS evolved.
If you yield a Promise, you can pause the function until the data arrives.
Note: You need a helper function to run this automatically, but here is the concept:
function* fetchUser() {
// Yield the promise. The 'runner' waits for it to resolve.
const user = yield fetch('/api/user');
// The 'runner' passes the resolved JSON back into the function
console.log(user.name);
}
This looks almost exactly like async/await, doesn't it?
-
function*is roughlyasync function -
yieldis roughlyawait
Summary: The Generator Cheatsheet
-
function*: Creates the generator. -
yield value: Pauses execution and outputsvalue. -
const x = yield: Pauses and waits for data to be passed in via.next(). -
generator.next(): Runs code until the nextyield. -
yield*: Delegates to another generator.
Generators give you manual control over the flow of time in your code. While async/await has replaced them for simple data fetching, Generators are still the kings of complex flows, infinite data streams, and state machines.
Top comments (0)