An ordinary function runs to the end: it starts, computes, returns a result, and its job is over. A generator function can stop halfway, hand back a value, and wait until someone asks for the rest.
yield is the keyword marking those stopping points. It is the only construct in the language that lets a function keep its local variables between two calls without storing them somewhere else.
Definition
yield hands a value to the calling code and pauses the generator function containing it, whose execution will resume at that exact line on the next call to next().
A generator function is declared with a star: function* name(). Calling it runs nothing; it creates an iterator, an object carrying a next() method.
function* counter() {
yield 1;
yield 2;
return 'done';
}
const it = counter();
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 'done', done: true }
it.next(); // { value: undefined, done: true }Each call returns an object with two keys: the value handed back, and a flag telling you whether the function has reached its end.
What yield is not
| Keyword | Effect on the function | Can it resume |
|---|---|---|
| return | Ends it for good | No |
yield | Pauses it | Yes, on the next next() |
| await | Pauses it until a promise settles | Automatically |
Worth noting: the value passed to return closes the series but is not part of it. A Spread (...) over this generator gives [1, 2], without the 'done'.
Endless sequences and delegation
Since nothing is computed before it is asked for, a generator function can describe an endless sequence without ever exhausting memory.
function* idSequence() {
let n = 1;
while (true) yield n++;
}
const ids = idSequence();
ids.next().value; // 1
ids.next().value; // 2The star also delegates: yield* hands over to another generator, or to any iterable value, and yields all of its values before carrying on.
function* inner() {
yield 'a';
yield 'b';
}
function* outer() {
yield 'start';
yield* inner();
yield 'end';
}
[...outer()]; // ['start', 'a', 'b', 'end']Communication runs both ways. The value passed to next() becomes the result of the waiting yield, which lets you write a function that asks a question, pauses, and resumes with the answer.
Frequently asked questions
What is this actually good for?
For sequences whose items you do not want to compute up front: paging through an API, reading a very large file chunk by chunk, unique identifiers. A generator produces what is asked for, when it is asked for, where an Array would require building everything first.
Can yield be written outside a generator function?
No, the file will not load: the keyword only means something inside a function declared with a star. The same error often comes from placing it in a nested arrow function, which cannot be a generator.
Do I need to master generators to work in JavaScript?
They are rarely written by hand, but you do meet them: asynchronous iterators, walked with for await, are built on them, and some state-management libraries use them to describe sequences of steps. Being able to read a function* is plenty before you ever have to write one.