The yield operator in generators
yield is an operator that returns a value out of a generator function and pauses its execution until the next .next() call. When the generator reaches a yield it freezes, and the following .next() resumes work right after the yield where it stopped.
Theory
TL;DR
yieldhands a value out in thevaluefield and stops the function where it stands.- The next
.next()continues execution from the line after thatyield. - It is a temporary
return: the value is returned, but the function is not finished. next(value)passes a value back in: it becomes the result of theyieldexpression.yield*delegates execution to another generator or to any iterable object.- Thanks to
yieldevaluation is lazy: values are produced only on demand.
Quick example
function* gen() {
console.log('Before the first yield');
yield 1;
console.log('Between the yields');
yield 2;
console.log('After the second yield');
}
const it = gen();
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: undefined, done: true }Step by step:
gen()returns an iterator object, the function has not started yet.it.next()starts the function, it runs toyield 1, returns{ value: 1 }and freezes.- The next
it.next()continues from the very same place, afteryield 1. - After the second
yieldthe generator freezes again. - When
done: truearrives, the generator has finished its work.
yield as a temporary return
function* seq() {
yield 'A';
yield 'B';
yield 'C';
}
const it = seq();
console.log(it.next().value); // "A"
console.log(it.next().value); // "B"
console.log(it.next().value); // "C"
console.log(it.next().done); // trueThe difference from return is fundamental: return finishes the function for good, while yield only hands out an intermediate value and leaves the function alive, with all of its local variables and its execution position intact.
Passing a value back in with next(value)
yield does not only send data out, it can also take a value back in on the following .next(value) call.
function* dialog() {
const name = yield 'What is your name?';
yield `Hello, ${name}!`;
}
const chat = dialog();
console.log(chat.next().value); // "What is your name?"
console.log(chat.next('Maria').value); // "Hello, Maria!"- The first
next()started the generator and ran it to the firstyield. - The second
next('Maria')passed'Maria'into the generator, and that value landed inconst name.
Finishing a generator: return() and throw()
A generator finishes when it reaches the end of its body, or if you call iterator.return(value). In the second case it immediately reports done: true.
function* numbers() {
yield 1;
yield 2;
}
const it = numbers();
console.log(it.next()); // { value: 1, done: false }
console.log(it.return('stop')); // { value: 'stop', done: true }
console.log(it.next()); // { value: undefined, done: true }In the same way you can inject an error straight into the generator, at the point of the current yield, and catch it there with an ordinary try/catch:
function* gen() {
try {
yield 1;
} catch (e) {
console.log('Error caught:', e.message);
}
}
const it = gen();
it.next();
it.throw(new Error('Something went wrong')); // Error caught: Something went wrongyield*: delegating to another generator
Sometimes one generator needs to hand control over to another. That is what yield* is for.
function* inner() {
yield 'B';
yield 'C';
}
function* outer() {
yield 'A';
yield* inner(); // hands control over to another generator
yield 'D';
}
for (const val of outer()) {
console.log(val);
}Output:
A
B
C
DIn essence yield* is a nested loop: just yield every value of the other generator. It works not only with generators but with any iterable object, for example yield* [1, 2, 3].
Laziness of evaluation
yield is what makes generators lazy: values are computed only on demand, not in advance.
function* range(start, end) {
for (let i = start; i <= end; i++) {
console.log('Generating', i);
yield i;
}
}
for (const num of range(1, 3)) {
console.log('Received:', num);
}Output:
Generating 1
Received: 1
Generating 2
Received: 2
Generating 3
Received: 3You can see that the generator creates values as they are needed rather than holding the whole range at once.
Summary:
| Feature | Description |
|---|---|
| What it does | Pauses execution and returns a value |
| Resuming | Through .next() |
| Can take values back in | Yes, next(value) |
| Can be finished | Through return() |
| Can be delegated | Through yield* |
| Evaluation | Lazy |
| Only works inside | function* (generators) |
Common mistakes
- Using
yieldoutside a generator. In a regular or arrow function that is a syntax error,yieldonly exists insidefunction*. - Expecting the first
next(value)to deliver a value. The first call only runs the body to the firstyield, so the argument you pass goes nowhere. - Confusing
yieldwithyield*.yield inner()hands out the generator object itself as a single value, whileyield* inner()hands out all of its values one by one. - Thinking
yieldfinishes the function. It does not, it only pauses it;returnor reaching the end of the body finishes it. - Forgetting a
try/catcharoundyield. Without one,it.throw()simply terminates the generator and rethrows the error outwards. - Building the whole array up front where a
yieldbelongs. That throws away the main benefit of generators, laziness and lower memory use.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.