Skip to main content

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 next yield.
  • 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

javascript
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:

  1. The iterator protocol: a next() method that returns an object with value and done fields.
  2. 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.

javascript
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 yield or return;
  • 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:

javascript
function* gen() { yield 1; yield 2; yield 3; } const iterator = gen(); for (const n of iterator) { console.log(n); }

Output:

javascript
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

MethodWhat 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:

javascript
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:

javascript
console.log([...numbers()]); // [1, 2]

or call .next() by hand a few times.

Visually it looks like this:

javascript
┌─────────────────────┐ function* numbers(){ yield 1; yield 2; }└────────┬────────────┘ │ call ┌───────────────────────────────┐ Object [Generator] │ ├── next() │ ├── throw() │ ├── return() │ └── [Symbol.iterator]() └───────────────────────────────┘

Summary:

What a generator returnsA 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...ofYes
How does it return a final valueThrough return, together with done: true

Common mistakes

  • Expecting an array. numbers() returns a generator object, not [1, 2]; spread or Array.from gives you the array.
  • Assuming the body has already run. Even a console.log at the top of the generator will not fire until the first .next().
  • Walking the same generator object twice. After done: true it is exhausted; to go through it again, call the generator function once more.
  • Confusing yielded values with the returned value. Whatever return produces arrives together with done: true and never shows up in for...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 having next and [Symbol.iterator].

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.