Understanding JavaScript Generators: Basics, Syntax, and Advanced Usage
JavaScript Generators, introduced in ES6, allow functions to pause and resume execution, yielding multiple values; this article explains their syntax, basic usage, advanced features like yield* and data exchange, and demonstrates practical scenarios such as asynchronous flow control, memory-efficient data processing, and state machine implementation.
JavaScript Generator functions, added in ES6, provide a way to produce a sequence of values while allowing the function to pause and later resume execution. Although they are less frequently used than async/await in many projects, they excel in complex flow‑control and state‑management scenarios.
Generator Overview
A JavaScript Generator is a special type of function that can generate multiple values over time. When a function is declared with an asterisk ( function* ), it becomes a generator. Inside the generator, the yield keyword defines each value to be returned to the caller.
Basic Syntax
Generators are defined using the function* syntax and can contain multiple yield expressions:
function* myGenerator() {
// Generator body
}Example:
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}Calling a generator does not execute its body immediately; it returns an iterator object. The iterator’s next() method runs the function until the next yield , returning an object with value and done properties.
const myGeneratorIterator = myGenerator();
console.log(myGeneratorIterator.next()); // { value: 1, done: false }
console.log(myGeneratorIterator.next()); // { value: 2, done: false }
console.log(myGeneratorIterator.next()); // { value: 3, done: false }
console.log(myGeneratorIterator.next()); // { value: undefined, done: true }Advanced Usage
yield* Expression
The yield* expression delegates yielding to another generator or iterable, effectively flattening nested sequences.
function* foo() {
yield 1;
yield 2;
}
function* bar() {
yield* foo();
yield 3;
}
for (let value of bar()) {
console.log(value); // 1, 2, 3
}Data Interaction
Generators can receive data from the caller via next(value) . The value passed to next() becomes the result of the paused yield expression.
function* foo() {
let x = yield;
yield x * 2;
}
let gen = foo();
gen.next(); // start generator
gen.next(10); // passes 10, yields 20Asynchronous Programming
By yielding Promise objects, generators can orchestrate asynchronous operations without async/await :
function* myGenerator() {
const result1 = yield new Promise(resolve => setTimeout(() => resolve('first'), 1000));
console.log(result1);
const result2 = yield new Promise(resolve => setTimeout(() => resolve('second'), 2000));
console.log(result2);
const result3 = yield new Promise(resolve => setTimeout(() => resolve('third'), 3000));
console.log(result3);
}
const generator = myGenerator();
const promise = generator.next().value;
promise.then(result => generator.next(result).value)
.then(result => generator.next(result).value)
.then(result => generator.next(result).value);Advantages and Disadvantages
More flexible control flow for complex asynchronous sequences.
Generator state can be saved and resumed, enabling reuse.
Works with a wide range of async patterns, including callbacks, events, and promises.
Higher code complexity and reduced readability compared to async/await .
Practical Scenarios
Controlling Asynchronous Flow
Fetching data from multiple APIs sequentially can be expressed cleanly with a generator:
function* fetchAllData() {
const data1 = yield fetch('api1');
const data2 = yield fetch('api2');
const data3 = yield fetch('api3');
return [data1, data2, data3];
}
function run(generator) {
const iterator = generator();
function handle(iteratorResult) {
if (iteratorResult.done) {
return Promise.resolve(iteratorResult.value);
}
return Promise.resolve(iteratorResult.value)
.then(res => handle(iterator.next(res)));
}
return handle(iterator.next());
}
run(fetchAllData).then(data => console.log(data));Processing Large Data Sets
Generators enable lazy evaluation, reducing memory usage when handling big data:
function* dataGenerator() {
let index = 0;
while (true) {
yield index++;
}
}
function* processData(data, processFn) {
for (let item of data) {
yield processFn(item);
}
}
const data = dataGenerator();
const processedData = processData(data, item => item * 2);
for (let i = 0; i < 500; i++) {
console.log(processedData.next().value);
}State Machine Implementation
Generators can model state machines, where each yield represents a transition:
function* stateMachine() {
let state = 'start';
while (true) {
switch (state) {
case 'start':
console.log('Enter start state');
state = yield 'start';
break;
case 'middle':
console.log('Enter middle state');
state = yield 'middle';
break;
case 'end':
console.log('Enter end state');
state = yield 'end';
break;
}
}
}
const sm = stateMachine();
console.log(sm.next().value); // start
console.log(sm.next('middle').value); // middle
console.log(sm.next('end').value); // endConclusion
While async/await offers a simpler syntax for most asynchronous tasks, Generator functions remain valuable for intricate control flows, custom state machines, and memory‑efficient data processing, providing developers with a powerful tool when needed.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.