What a generator call returns
Calling a generator function (function*) does not run its code right away: it returns a generator object, which is an iterator and controls the execution of that function. In other words you get back not a result but a means of running the function body in portions.
Theory
TL;DR
gen()does not execute a single line of the body, it creates a generator object.- That object is an iterator: it has
.next(),.throw()and.return(). - At the same time it is an iterable, because it has
[Symbol.iterator](). - Every
.next()returns{ value, done }and runs execution up to the nextyield. - To get all the values at once you have to walk the generator:
for...of, spread,Array.from. - A generator is not the result of the function:
numbers()is not[1, 2].
Quick example
function* gen() {
yield 1;
yield 2;
yield 3;
}
const iterator = gen(); // a generator object comes back
console.log(iterator); // Object [Generator] {}The iterator object is not the function's result, it is a controller that lets you run the generator step by step through .next(), .throw(), .return().
The two protocols a generator implements
A generator object implements two protocols at once:
- The iterator protocol: a
next()method that returns an object withvalueanddonefields. - The iterable protocol: a
[Symbol.iterator]()method that returns the generator itself.
That is exactly why the same object can be cranked by hand with .next() and also handed to for...of.
function* gen() {
yield 'A';
yield 'B';
}
const it = gen();
console.log(it.next()); // { value: 'A', done: false }
console.log(it.next()); // { value: 'B', done: false }
console.log(it.next()); // { value: undefined, done: true }Every .next() call:
- starts the generator, if it is frozen;
- runs it up to the next
yieldorreturn; - returns an object
{ value, done }.
A generator in for...of, spread and Array.from
Because a generator is iterable, you can use it anywhere an iterable is expected:
function* gen() {
yield 1;
yield 2;
yield 3;
}
const iterator = gen();
for (const n of iterator) {
console.log(n);
}Output:
1
2
3[...gen()] and Array.from(gen()) work the same way. One caveat: walking a generator exhausts it, so a second for...of over the same object produces nothing, you need a fresh gen() call.
The three methods of a generator object
| Method | What it does |
|---|---|
.next(value) | Resumes execution up to the next yield; returns { value, done } |
.throw(error) | Raises an exception inside the generator (caught with try/catch) |
.return(value) | Finishes the generator and returns { value, done: true } |
A generator is not the function's result
This is the part people forget most often:
function* numbers() {
yield 1;
yield 2;
}
const result = numbers(); // returns a generator object, not [1, 2]To get the real values you have to walk the generator:
console.log([...numbers()]); // [1, 2]or call .next() by hand a few times.
Visually it looks like this:
┌─────────────────────┐
│ function* numbers() │
│ { yield 1; yield 2; }│
└────────┬────────────┘
│ call
▼
┌───────────────────────────────┐
│ Object [Generator] │
│ ├── next() │
│ ├── throw() │
│ ├── return() │
│ └── [Symbol.iterator]() │
└───────────────────────────────┘Summary:
| What a generator returns | A generator object |
|---|---|
| Does it start immediately? | No |
| Can you control execution? | Yes, through .next() |
| Does it implement the iterator protocol? | Yes |
| How does it return values? | Through yield |
Can it be used in for...of | Yes |
| How does it return a final value | Through return, together with done: true |
Common mistakes
- Expecting an array.
numbers()returns a generator object, not[1, 2]; spread orArray.fromgives you the array. - Assuming the body has already run. Even a
console.logat the top of the generator will not fire until the first.next(). - Walking the same generator object twice. After
done: trueit is exhausted; to go through it again, call the generator function once more. - Confusing yielded values with the returned value. Whatever
returnproduces arrives together withdone: trueand never shows up infor...of. - Forgetting that
.return()finishes the generator for good. Subsequent.next()calls only hand back{ value: undefined, done: true }. - Checking
typeof gen(). It is always'object', so testing the type that way is pointless; what marks a generator is havingnextand[Symbol.iterator].
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.