JavaScript Generators
Learn how generator functions pause and resume execution to build lazy sequences and iterables.
TL;DR
- 01Declare generators with
function*to suspend and resume functions. - 02Emit values lazily on demand using the
yieldkeyword. - 03Forward iteration sequences to external collections using
yield*delegation.
Tips
- 01Use generators to compute massive data sequences lazily without consuming system memory up front.
- 02Assign generator methods to
Symbol.iteratorproperties to create custom iterables cleanly.
Warnings
- 01Catch exceptions thrown inside generator scopes to prevent them from closing the iterator permanently.
- 02Avoid using spread syntax on infinite generators to prevent crashing the browser thread.
Generator Basics
function* declarationDeclares generators which return an iterator object instead of running code.
function* counter() {
yield 1;
yield 2;
}
const it = counter();next() callsResumes execution block internally until encountering the next yield line.
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }Return valuesSignals completion with done true and returns standard values if declared.
function* range() {
yield 1;
return "stop";
}
const it2 = range();
it2.next(); // { value: 1, done: false }
it2.next(); // { value: "stop", done: true }Controlling Generators
Value injectionPasses parameter values back into the generator at pause lines.
function* greet() {
const name = yield "name?";
yield `Hi, ${name}`;
}
const g = greet();
g.next(); // "name?"
g.next("Ada"); // { value: "Hi, Ada", done: false }return() methodTerminates generator runs early, returning specified values immediately.
const it = counter();
it.next();
it.return("end"); // { value: "end", done: true }throw() methodInjects exceptions directly into generators at the current yield line.
function* safe() {
try { yield 1; } catch (e) { yield e.message; }
}
const it = safe();
it.next();
it.throw(new Error("oops")); // value: "oops"Lazy Sequences
Infinite generatorsComputes unending data streams on demand with zero memory leaks.
function* naturals() {
let n = 1;
while (true) yield n++;
}
const it = naturals();
it.next().value; // 1take() boundariesExtracts limited arrays from lazy sequences using break counters.
function take(iterable, count) {
const res = [];
for (const v of iterable) {
if (res.length >= count) break;
res.push(v);
}
return res;
}
take(naturals(), 3); // [1, 2, 3]Custom Iterables
Symbol.iterator methodAttaches generators to class object structures to allow for-of loops.
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
[...new Range(1, 3)]; // [1, 2, 3]Delegation with yield*
Iterable forwardingDelegates execution directly to another iterable structure, avoiding loops.
function* combine() {
yield* [1, 2];
yield* "ab";
}
[...combine()]; // [1, 2, "a", "b"]In Practice
Generates an infinite Fibonacci sequence lazily using destructuring assignment and a custom take controller.
- 01Initialize variables to store the two initial sequence values.
- 02Establish an infinite loop that yields numbers on demand.
- 03Yield the current sequence value back to the caller.
- 04Calculate the subsequent numbers using destructuring array assignments.
- 05Pull a subset array of values without triggering infinite processing.
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
function take(generator, count) {
const result = [];
for (const value of generator) {
if (result.length >= count) break;
result.push(value);
}
return result;
}
console.log(take(fibonacci(), 5)); // [1, 1, 2, 3, 5]FAQ
Regular functions run to completion immediately when invoked. Generator functions, declared with function*, return an iterator instead. The function body runs only when calling .next().
The yield keyword pauses generator function execution and outputs a value to the caller. The generator remains frozen until the caller invokes .next() again.
Use yield* to forward values from another iterable, like an array or generator. This avoids manual loops and passes .next(), .return(), and .throw() down automatically.
The .return(value) method closes the generator early, returning the value. The .throw(error) method injects an exception at the current pause point, letting try/catch handle it.
Yes, they are. Assign a generator function to the object's Symbol.iterator property. The engine handles iterator tracking and value formats automatically under the hood.