Generators are not only for producing values. They can also return a final value and receive values.
return
A return inside a generator ends it. The returned value appears in the final { done: true } result, but for...of and spread ignore it.
function* steps() {
yield "one";
yield "two";
return "finished";
}
const g = steps();
console.log(g.next()); // → { value: one, done: false }
console.log(g.next()); // → { value: two, done: false }
console.log(g.next()); // → { value: finished, done: true }
console.log([...steps()]); // → ["one", "two"] (the return value is not included)
Sending a value in: next(value)
The argument you pass to next(value) becomes the result of the yield expression where the generator is paused.
function* conversation() {
const name = yield "What is your name?";
const mood = yield `Nice to meet you, ${name}. How are you?`;
return `${name} is feeling ${mood}.`;
}
const talk = conversation();
console.log(talk.next().value); // → What is your name?
console.log(talk.next("Maya").value); // → Nice to meet you, Maya. How are you?
console.log(talk.next("great").value); // → Maya is feeling great.
The first next() call starts the generator (its argument would be ignored). Each later argument answers the previous yield.
Ending early: return() and throw()
function* numbers() {
try {
yield 1;
yield 2;
yield 3;
} finally {
console.log("cleanup");
}
}
const g = numbers();
console.log(g.next().value); // → 1
console.log(g.return("stopped")); // → { value: stopped, done: true } (and "cleanup" is printed)
g.throw(error) throws an error inside the generator at the paused yield, where it can be caught.
function* guarded() {
try {
yield 1;
} catch (error) {
console.log("generator caught:", error.message);
yield "recovered";
}
}
const g = guarded();
g.next();
console.log(g.throw(new Error("boom")).value); // → recovered
States of a generator
| State | Meaning |
|---|---|
| suspended at start | created, not started |
suspended at yield | paused, waiting for next() |
| running | currently executing |
| completed | returned or threw; next() now gives { value: undefined, done: true } |
Where this is used
Two-way communication is the idea behind some older async libraries (before async/await) and behind state machines. In everyday code you will mostly use the simple form: generators that yield values.
Keep it simple
yield. Reach for next(value) and throw() only when you have a clear reason.