Generators and yield
A generator (function*) is a function that can pause its execution, hand out intermediate values and then continue from exactly the same place. Formally, function* returns a special object, an iterator, which drives the execution of the function body.
Theory
TL;DR
function*declares a generator; calling it does not run the code, it returns an iterator object.yieldpauses execution, hands a value out as{ value, done }and waits for the next.next().gen.next(value)pushes a value back in: it becomes the result of theyieldthe generator is paused on..return(value)finishes the generator early,.throw(error)throws an exception inside it that can be caught withtry...catch.- A generator automatically implements both the iterator protocol (
next()) and the iterable protocol ([Symbol.iterator]), so it works infor...of. - Generators are perfect for lazy computation and infinite sequences: data is produced in portions, without building huge arrays.
Quick example
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = myGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }How a generator is declared and what the call returns
A generator is declared with an asterisk after the function keyword:
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}The key point: calling myGenerator() does not execute the function body. The call returns an iterator that you have to "crank" manually by calling .next(). Each such call runs the function up to the next yield and returns an object { value, done }, where value is the value handed out and done tells you whether the function has already reached its end.
An object or class method can be a generator too, with the asterisk in front of the name: *items() { ... }. An arrow function, however, can never be a generator.
What yield does
The yield operator does three things:
- Pauses execution of the function exactly at this point.
- Hands a value out, into the result of
.next(). - Lets you pass a value back in on the following
.next()call.
Example 1, a simple sequence:
function* counter() {
yield 1;
yield 2;
yield 3;
}
const gen = counter();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }Example 2, pauses between steps:
function* steps() {
console.log("Step 1");
yield;
console.log("Step 2");
yield;
console.log("Step 3");
}
const gen = steps();
gen.next(); // "Step 1"
gen.next(); // "Step 2"
gen.next(); // "Step 3"Here yield is used without a value, simply as a stopping point.
How to pass a value back into a generator
When you call gen.next(value), that value lands inside the generator exactly where execution was paused by yield. In other words, the expression yield "..." evaluates to whatever you passed into the following .next().
Example 3, passing a value back:
function* dialog() {
const name = yield "What is your name?";
const age = yield `Hello, ${name}! How old are you?`;
yield `You are ${age} years old.`;
}
const gen = dialog();
console.log(gen.next()); // { value: "What is your name?", done: false }
console.log(gen.next("Maria")); // { value: "Hello, Maria! How old are you?", done: false }
console.log(gen.next(25)); // { value: "You are 25 years old.", done: false }
console.log(gen.next()); // { value: undefined, done: true }Step by step:
- The first
next()starts the generator and returns the value of the firstyield. - The second
next("Maria")inserts the string"Maria"where the firstyieldstood, sonamereceives that value. - The third
next(25)inserts25in place of the secondyield, soageequals25.
Controlling a generator: next, return and throw
The generator iterator has three control methods:
| Method | What it does |
|---|---|
.next(value) | Resumes execution and returns the next { value, done } |
.return(value) | Force-finishes the generator, returns { value, done: true } |
.throw(error) | Throws an exception inside the generator, catchable with try...catch |
Example 5, return() and throw():
function* process() {
try {
yield "Step 1";
yield "Step 2";
} catch (e) {
console.log("Error:", e.message);
} finally {
console.log("Generator finished");
}
}
const gen = process();
console.log(gen.next()); // { value: "Step 1", done: false }
console.log(gen.throw(new Error("Something went wrong")));
// Error: Something went wrong
// Generator finishedThe finally block also runs when the generator is stopped with .return(), which makes it the right place for resource cleanup.
Generators as iterators, lazy and async sequences
A generator automatically implements the iterator interface (next()) and the iterable interface ([Symbol.iterator]), so you can hand it straight to for...of, to spread or to destructuring:
function* numbers() {
yield 1;
yield 2;
yield 3;
}
for (const n of numbers()) {
console.log(n); // 1, 2, 3
}Example 4, an infinite sequence:
function* infinite() {
let i = 0;
while (true) {
yield i++;
}
}
const gen = infinite();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2This code does not hang, because values are computed lazily: the next number appears only when it is asked for through .next().
Bonus, generators and asynchrony. Before async/await existed, generators were exactly what people used for asynchronous code (through libraries such as co):
function* getData() {
const user = yield fetch('/user');
const posts = yield fetch(`/posts?user=${user.id}`);
return posts;
}async/await was later built on the very same idea; it is essentially an "automated generator" where the engine calls .next() for you once the promise settles.
Summary table:
| Concept | Description |
|---|---|
function* | Generator declaration |
yield | Pauses execution and hands a value out |
.next(value) | Resumes execution, passing value in |
.return(value) | Finishes the generator |
.throw(error) | Throws an exception into the generator |
| Generator | An iterator object whose execution can be paused and resumed |
Common mistakes
- Thinking that calling
myGenerator()runs the function body. It only creates an iterator; the first line runs on the first.next(). - Expecting the argument of the first
.next(value)to land somewhere. The first call only starts the generator, and the passed value is discarded, because there is noyieldyet that could receive it. - Mixing up the directions. What goes out is the value to the right of
yield; what comes in is the argument of the following.next(). - Running an infinite generator through
for...ofwithout abreak. The loop never ends, becausedonenever becomestrue. - Forgetting that a generator is single-use. After
done: trueor after.return(), every further.next()gives{ value: undefined, done: true }; to walk the sequence again you need a new generator. - Trying to declare a generator as an arrow function.
function*works only with regular functions and methods (*method() {}). - Treating a generator as asynchronous by itself. A plain generator is synchronous; asynchronous sequences need
async function*andfor await...of.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.