Skip to main content

Generators and yield

A generator (function*) is a function that can pause its execution, hand out intermediate values and then continue from exactly the same place. Formally, function* returns a special object, an iterator, which drives the execution of the function body.

Theory

TL;DR

  • function* declares a generator; calling it does not run the code, it returns an iterator object.
  • yield pauses execution, hands a value out as { value, done } and waits for the next .next().
  • gen.next(value) pushes a value back in: it becomes the result of the yield the generator is paused on.
  • .return(value) finishes the generator early, .throw(error) throws an exception inside it that can be caught with try...catch.
  • A generator automatically implements both the iterator protocol (next()) and the iterable protocol ([Symbol.iterator]), so it works in for...of.
  • Generators are perfect for lazy computation and infinite sequences: data is produced in portions, without building huge arrays.

Quick example

javascript
function* myGenerator() { yield 1; yield 2; yield 3; } const gen = myGenerator(); console.log(gen.next()); // { value: 1, done: false } console.log(gen.next()); // { value: 2, done: false } console.log(gen.next()); // { value: 3, done: false } console.log(gen.next()); // { value: undefined, done: true }

How a generator is declared and what the call returns

A generator is declared with an asterisk after the function keyword:

javascript
function* myGenerator() { yield 1; yield 2; yield 3; }

The key point: calling myGenerator() does not execute the function body. The call returns an iterator that you have to "crank" manually by calling .next(). Each such call runs the function up to the next yield and returns an object { value, done }, where value is the value handed out and done tells you whether the function has already reached its end.

An object or class method can be a generator too, with the asterisk in front of the name: *items() { ... }. An arrow function, however, can never be a generator.

What yield does

The yield operator does three things:

  1. Pauses execution of the function exactly at this point.
  2. Hands a value out, into the result of .next().
  3. Lets you pass a value back in on the following .next() call.

Example 1, a simple sequence:

javascript
function* counter() { yield 1; yield 2; yield 3; } const gen = counter(); console.log(gen.next()); // { value: 1, done: false } console.log(gen.next()); // { value: 2, done: false } console.log(gen.next()); // { value: 3, done: false } console.log(gen.next()); // { value: undefined, done: true }

Example 2, pauses between steps:

javascript
function* steps() { console.log("Step 1"); yield; console.log("Step 2"); yield; console.log("Step 3"); } const gen = steps(); gen.next(); // "Step 1" gen.next(); // "Step 2" gen.next(); // "Step 3"

Here yield is used without a value, simply as a stopping point.

How to pass a value back into a generator

When you call gen.next(value), that value lands inside the generator exactly where execution was paused by yield. In other words, the expression yield "..." evaluates to whatever you passed into the following .next().

Example 3, passing a value back:

javascript
function* dialog() { const name = yield "What is your name?"; const age = yield `Hello, ${name}! How old are you?`; yield `You are ${age} years old.`; } const gen = dialog(); console.log(gen.next()); // { value: "What is your name?", done: false } console.log(gen.next("Maria")); // { value: "Hello, Maria! How old are you?", done: false } console.log(gen.next(25)); // { value: "You are 25 years old.", done: false } console.log(gen.next()); // { value: undefined, done: true }

Step by step:

  • The first next() starts the generator and returns the value of the first yield.
  • The second next("Maria") inserts the string "Maria" where the first yield stood, so name receives that value.
  • The third next(25) inserts 25 in place of the second yield, so age equals 25.

Controlling a generator: next, return and throw

The generator iterator has three control methods:

MethodWhat it does
.next(value)Resumes execution and returns the next { value, done }
.return(value)Force-finishes the generator, returns { value, done: true }
.throw(error)Throws an exception inside the generator, catchable with try...catch

Example 5, return() and throw():

javascript
function* process() { try { yield "Step 1"; yield "Step 2"; } catch (e) { console.log("Error:", e.message); } finally { console.log("Generator finished"); } } const gen = process(); console.log(gen.next()); // { value: "Step 1", done: false } console.log(gen.throw(new Error("Something went wrong"))); // Error: Something went wrong // Generator finished

The finally block also runs when the generator is stopped with .return(), which makes it the right place for resource cleanup.

Generators as iterators, lazy and async sequences

A generator automatically implements the iterator interface (next()) and the iterable interface ([Symbol.iterator]), so you can hand it straight to for...of, to spread or to destructuring:

javascript
function* numbers() { yield 1; yield 2; yield 3; } for (const n of numbers()) { console.log(n); // 1, 2, 3 }

Example 4, an infinite sequence:

javascript
function* infinite() { let i = 0; while (true) { yield i++; } } const gen = infinite(); console.log(gen.next().value); // 0 console.log(gen.next().value); // 1 console.log(gen.next().value); // 2

This code does not hang, because values are computed lazily: the next number appears only when it is asked for through .next().

Bonus, generators and asynchrony. Before async/await existed, generators were exactly what people used for asynchronous code (through libraries such as co):

javascript
function* getData() { const user = yield fetch('/user'); const posts = yield fetch(`/posts?user=${user.id}`); return posts; }

async/await was later built on the very same idea; it is essentially an "automated generator" where the engine calls .next() for you once the promise settles.

Summary table:

ConceptDescription
function*Generator declaration
yieldPauses execution and hands a value out
.next(value)Resumes execution, passing value in
.return(value)Finishes the generator
.throw(error)Throws an exception into the generator
GeneratorAn iterator object whose execution can be paused and resumed

Common mistakes

  • Thinking that calling myGenerator() runs the function body. It only creates an iterator; the first line runs on the first .next().
  • Expecting the argument of the first .next(value) to land somewhere. The first call only starts the generator, and the passed value is discarded, because there is no yield yet that could receive it.
  • Mixing up the directions. What goes out is the value to the right of yield; what comes in is the argument of the following .next().
  • Running an infinite generator through for...of without a break. The loop never ends, because done never becomes true.
  • Forgetting that a generator is single-use. After done: true or after .return(), every further .next() gives { value: undefined, done: true }; to walk the sequence again you need a new generator.
  • Trying to declare a generator as an arrow function. function* works only with regular functions and methods (*method() {}).
  • Treating a generator as asynchronous by itself. A plain generator is synchronous; asynchronous sequences need async function* and for await...of.

Short Answer

Interview ready
Premium

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