Skip to main content

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

  • yield hands a value out in the value field and stops the function where it stands.
  • The next .next() continues execution from the line after that yield.
  • 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 the yield expression.
  • yield* delegates execution to another generator or to any iterable object.
  • Thanks to yield evaluation is lazy: values are produced only on demand.

Quick example

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

  1. gen() returns an iterator object, the function has not started yet.
  2. it.next() starts the function, it runs to yield 1, returns { value: 1 } and freezes.
  3. The next it.next() continues from the very same place, after yield 1.
  4. After the second yield the generator freezes again.
  5. When done: true arrives, the generator has finished its work.

yield as a temporary return

javascript
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); // true

The 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.

javascript
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 first yield.
  • The second next('Maria') passed 'Maria' into the generator, and that value landed in const 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.

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

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

yield*: delegating to another generator

Sometimes one generator needs to hand control over to another. That is what yield* is for.

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

javascript
A B C D

In 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.

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

javascript
Generating 1 Received: 1 Generating 2 Received: 2 Generating 3 Received: 3

You can see that the generator creates values as they are needed rather than holding the whole range at once.

Summary:

FeatureDescription
What it doesPauses execution and returns a value
ResumingThrough .next()
Can take values back inYes, next(value)
Can be finishedThrough return()
Can be delegatedThrough yield*
EvaluationLazy
Only works insidefunction* (generators)

Common mistakes

  • Using yield outside a generator. In a regular or arrow function that is a syntax error, yield only exists inside function*.
  • Expecting the first next(value) to deliver a value. The first call only runs the body to the first yield, so the argument you pass goes nowhere.
  • Confusing yield with yield*. yield inner() hands out the generator object itself as a single value, while yield* inner() hands out all of its values one by one.
  • Thinking yield finishes the function. It does not, it only pauses it; return or reaching the end of the body finishes it.
  • Forgetting a try/catch around yield. Without one, it.throw() simply terminates the generator and rethrows the error outwards.
  • Building the whole array up front where a yield belongs. That throws away the main benefit of generators, laziness and lower memory use.

Short Answer

Interview ready
Premium

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