How JavaScript Generators and Yield Work: A Complete Guide to `function*` and Iteration Control

JavaScript generators are special functions declared with function* that use the yield keyword to pause execution and resume later, returning a generator object that conforms to the iterator protocol.

The lydiahallie/javascript-questions repository provides comprehensive documentation on how JavaScript generators and yield enable memory-efficient iteration and asynchronous data streams. This guide examines the source code at specific line ranges in README.md to explain the mechanics of generator objects, state suspension, and delegation patterns.

What Are JavaScript Generators?

Unlike regular functions that run to completion upon invocation, generator functions use the function* syntax to return a generator object immediately without executing the function body. As documented in README.md around line 1345, this generator object implements the iterator protocol, exposing next(), return(), and throw() methods.

When you invoke a generator function, JavaScript creates an internal state machine that tracks local variables, the current this context, and the execution pointer. This state remains frozen between calls, allowing the function to resume exactly where it left off.

How the yield Keyword Controls Execution

The yield keyword is the mechanism that pauses generator execution. Each time the generator encounters a yield expression, it produces the supplied value and suspends its internal state. According to the source analysis of lydiahallie/javascript-questions, this suspension includes preserving local variable bindings and the execution context.

When you call gen.next() on the generator object, execution resumes from the exact point of the last yield. The next() method returns an object with two properties:

  • value: The value yielded by the generator
  • done: A boolean indicating whether the generator has completed

Once the generator function exhausts all yield statements and reaches the end of the function body, subsequent calls to next() return { value: undefined, done: true }.

Delegating Iteration with yield*

The yield* expression delegates iteration to another generator or iterable object. As shown in README.md around line 3643, yield* forwards control to the inner iterator, yielding each of its values in turn before resuming the outer generator.

This delegation pattern allows you to compose generators cleanly, reusing existing iteration logic without manually looping through the inner iterable. The outer generator pauses until the delegated iterator completes, then continues execution with the next statement after the yield* expression.

Async Generators and for await...of

JavaScript extends generator concepts to asynchronous contexts with async function* syntax. As documented near line 4036 in README.md, async generators yield Promises instead of immediate values, enabling consumption via for await...of loops or manual await gen.next() calls.

This pattern is particularly effective for streaming data sources where values arrive over time, such as reading files line-by-line or processing network chunks. The async generator handles backpressure naturally, pausing execution until the consumer requests the next value.

Practical Code Examples from the Source

The lydiahallie/javascript-questions repository provides concrete implementations demonstrating these concepts. Below are the canonical examples referenced in the source analysis.

Basic Generator with yield

This example from README.md (lines 1345-1360) demonstrates the fundamental pause-and-resume behavior:

// Basic generator – yields numbers 0, 1, 2
function* countToTwo() {
  yield 0;
  yield 1;
  yield 2;
}
const gen = countToTwo();
console.log(gen.next()); // { value: 0, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: undefined, done: true }

Delegating with yield*

The delegation pattern shown around line 3643 illustrates composition:

// Delegating with yield*
function* letters() {
  yield* ['a', 'b', 'c']; // forwards to the array iterator
}
function* combined() {
  yield* countToTwo();    // re‑use the previous generator
  yield* letters();       // then yield letters
}
for (const v of combined()) {
  console.log(v);
}
// Output: 0 1 2 a b c

Async Generator for Streaming Data

The async implementation near line 4036 demonstrates Promise-based iteration:

// Async generator – streams numbers with a delay
async function* slowRange(start, end) {
  for (let i = start; i <= end; i++) {
    await new Promise(r => setTimeout(r, 500)); // 0.5 s pause
    yield i;
  }
}
(async () => {
  for await (const n of slowRange(1, 3)) {
    console.log(n); // logs 1, then 2, then 3 every 0.5 s
  }
})();

Infinite Sequences with Memory Efficiency

This pattern demonstrates lazy evaluation for unbounded sequences:

// Using a generator to implement a simple iterator protocol
function* fibonacci() {
  let a = 0, b = 1;
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2

Summary

  • JavaScript generators use function* syntax to create objects that implement the iterator protocol, allowing functions to pause and resume execution while maintaining internal state.
  • The yield keyword suspends the generator, preserving local variables and execution context, while gen.next() resumes operation returning { value, done }.
  • yield* delegates iteration to another generator or iterable, enabling composition of complex iteration logic without manual looping.
  • Async generators (async function*) extend these patterns to Promise-based streams, consumable via for await...of loops for handling asynchronous data sources.
  • The lydiahallie/javascript-questions repository documents these patterns in README.md at lines 1345, 3643, and 4036, with translations available in zh-CN/, es-ES/, and fr-FR/ directories.

Frequently Asked Questions

What is the difference between yield and return in a JavaScript generator?

While both yield and return produce values, yield pauses the generator and preserves its internal state, allowing subsequent next() calls to resume execution from the exact suspension point. return terminates the generator permanently, setting the done property to true and preventing further iteration. Once a generator returns, subsequent calls to next() will continue to return { value: undefined, done: true }.

Can I use yield outside of a generator function?

No, the yield keyword is syntactically valid only inside generator functions declared with function* or async function*. Using yield in a regular function, arrow function, or method will throw a SyntaxError. This restriction exists because yield requires the JavaScript engine to create the special execution context and state management infrastructure that generators provide.

How does yield* differ from calling a generator function directly?

yield* delegates iteration to another generator or iterable, yielding each value from the inner source as if it came directly from the outer generator. Simply calling a generator function (innerGen()) returns a generator object without consuming it—you would need to manually iterate and yield each value. yield* handles this automatically, preserving the return value of the delegated generator as the result of the yield* expression itself.

When should I use an async generator instead of a regular generator?

Use async generators (async function*) when your iteration logic involves asynchronous operations such as fetching data from APIs, reading streams, or database queries that return Promises. Regular generators cannot handle Promises directly—they execute synchronously. Async generators allow you to await asynchronous operations before yielding values, and they are consumed with for await...of loops or manual await gen.next() calls, making them ideal for streaming asynchronous data sources without loading entire datasets into memory.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →