JavaScript Generators: The 'Pause' Button for Your Code
Source: Dev.to
Introduction
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. 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. Think of a normal function as a rollercoaster you must ride to the end, while a generator is more like a Netflix series—you can watch a bit, pause, do something else, and then continue from the same spot.
Defining a Generator
Two special pieces of syntax are used:
function*– the asterisk tells JavaScript that this is a generator.yield– the “pause” button.
function* myGenerator() {
console.log("1. Starting...");
yield "First Pause"; // Pauses here and outputs "First Pause"
console.log("2. Resuming...");
yield "Second Pause"; // Pauses here and outputs "Second Pause"
console.log("3. Finishing!");
return "Done";
}
Creating and Running a Generator
Calling a generator function does not execute its code; it returns a generator object (an iterator) that acts like a remote control.
const gen = myGenerator(); // No console logs happen here!
To start execution, call .next() on the generator object. Each call runs the code until the next yield (or return) and returns an object:
value– whatever follows theyield.done–falsewhile the generator can continue,truewhen it’s finished.
console.log(gen.next());
// Output:
// "1. Starting..."
// { value: "First Pause", done: false }
console.log(gen.next());
// Output:
// "2. Resuming..."
// { value: "Second Pause", done: false }
console.log(gen.next());
// Output:
// "3. Finishing!"
// { value: "Done", done: true }
Sending Data Into a Generator
yield can also receive data. When you call .next(value), the supplied value becomes the result of the paused yield expression.
function* chattyGenerator() {
const name = yield "What is your name?";
const age = yield "How old are you?";
return `Hello ${name}, you are ${age}!`;
}
const gen2 = chattyGenerator();
console.log(gen2.next().value); // "What is your name?"
console.log(gen2.next("John").value); // "How old are you?"
console.log(gen2.next(30).value); // "Hello John, you are 30!"
Lazy (On‑Demand) Data Generation
Generators are ideal for producing potentially infinite sequences without exhausting memory.
function* idGenerator() {
let id = 1;
while (true) {
yield id;
id++;
}
}
const ids = idGenerator();
console.log(ids.next().value); // 1
console.log(ids.next().value); // 2
console.log(ids.next().value); // 3
Delegating to Another Generator (yield*)
yield* forwards control to another generator, allowing composition.
function* guestDJ() {
yield "Song A";
yield "Song B";
}
function* mainDJ() {
yield "Intro";
yield* guestDJ(); // Hand over control until guestDJ finishes
yield "Outro";
}
for (const song of mainDJ()) {
console.log(song);
}
// Output:
// Intro
// Song A
// Song B
// Outro
Generators and Asynchronous Code
Before async/await, libraries like Redux‑Saga used generators to handle async flows. If a generator yields a Promise, a runner can pause execution until the promise resolves.
function* fetchUser() {
// Yield the promise; the runner waits for it to resolve.
const user = yield fetch('/api/user');
// The resolved value is passed back into the generator.
console.log(user.name);
}
Conceptually, function* is similar to async function, and yield behaves like await.
Summary – Generator Cheatsheet
| Syntax | Meaning |
|---|---|
function* | Declares a generator function |
yield value | Pauses execution and outputs value |
const x = yield | Pauses and waits for data passed via .next() |
generator.next() | Runs code until the next yield (or return) |
yield* otherGenerator() | Delegates to another generator |
Generators give you manual control over the flow of time in your code. While async/await has largely replaced them for simple data fetching, generators remain powerful for complex flows, infinite data streams, and state‑machine implementations.