How JavaScript Generators and Yield Work
Generator functions and the yield statement provide a
unique way to control execution flow in JavaScript by allowing a
function to pause its execution and resume it later. Unlike standard
functions that run to completion and return a single value, generator
functions produce a sequence of values on demand. This article explains
the syntax of generator functions, how yield pauses and
passes data, how the .next() method controls execution, and
the practical use cases for these tools.
Defining a Generator Function
A generator function is declared using the function*
syntax (an asterisk following the function keyword). When
invoked, a generator function does not execute its body immediately.
Instead, it returns a special object called a Generator object, which
conforms to both the iterable and iterator protocols.
function* simpleGenerator() {
console.log("Execution started");
yield 1;
console.log("Execution resumed");
yield 2;
}
const gen = simpleGenerator(); // Does not run the code yetThe Role of the
yield Statement
The yield keyword is used inside a generator function to
pause execution and emit a value to the caller. When the engine
encounters a yield, the function’s state (including local
variables and execution position) is frozen, and control returns to the
calling context.
Each time execution is paused at a yield, the generator
returns an object with two properties: - value: The data
following the yield keyword. - done: A boolean
indicating whether the generator has finished (false if
there are more yields, true if the function has ended or
returned).
Controlling Execution with
.next()
To start or resume a generator, you call the .next()
method on the generator object. Execution proceeds until it hits the
next yield or return statement.
function* countToThree() {
yield 1;
yield 2;
yield 3;
}
const counter = countToThree();
console.log(counter.next()); // { value: 1, done: false }
console.log(counter.next()); // { value: 2, done: false }
console.log(counter.next()); // { value: 3, done: false }
console.log(counter.next()); // { value: undefined, done: true }Two-Way
Communication: Passing Arguments to .next()
The yield expression not only produces values but can
also receive values. When you pass an argument to
.next(value), that argument becomes the result of the
yield expression currently paused inside the function.
function* conversation() {
const reply = yield "What is your name?";
yield `Hello, ${reply}!`;
}
const chat = conversation();
console.log(chat.next().value); // "What is your name?"
console.log(chat.next("Alice").value); // "Hello, Alice!"Note: The first call to .next() cannot pass a value
because it merely starts the generator up to the first
yield.
Delegating Generators with
yield*
The yield* expression allows a generator function to
delegate its iteration to another generator or iterable object (such as
an Array).
function* subRoutine() {
yield "A";
yield "B";
}
function* mainRoutine() {
yield 1;
yield* subRoutine();
yield 2;
}
const iterator = mainRoutine();
// Yields: 1, "A", "B", 2Terminating and Handling Errors
Generators have built-in methods for handling exceptions and early termination:
.return(value): Immediately ends the generator and returns{ value: value, done: true }..throw(error): Injects an error into the generator at the current paused location, which can be caught using a standardtry...catchblock inside the generator.
function* cancellable() {
try {
yield "Working...";
} catch (err) {
console.log("Caught:", err);
}
}
const task = cancellable();
task.next();
task.throw(new Error("Stop task")); // Triggers the catch blockCommon Use Cases
- Lazy Evaluation and Infinite Sequences: Generators compute values only when requested, making them ideal for generating large or infinite data sets (such as unique IDs or Fibonacci numbers) without consuming excessive memory.
- Custom Iterables: You can attach a generator
function to an object’s
[Symbol.iterator]method to make the object directly iterable withfor...ofloops. - State Machines: Because generators maintain their internal state between calls, they are effective tools for modeling complex state transitions cleanly.