JavaScript Generators Tutorial - Section 2: Fundamentals
The next() method advances the generator and optionally passes a value.
function* example() {
const a = yield 1;
const b = yield 2;
return a + b;
}
const gen = example();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next(10)); // { value: 2, done: false }
console.log(gen.next(20)); // { value: 30, done: true }
The return() method terminates the generator early and returns a value.
function* numbers() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
const gen = numbers();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.return('stopped')); // { value: 'stopped', done: true }
console.log(gen.next()); // { value: undefined, done: true }
function* withCleanup() {
try {
yield 1;
yield 2;
yield 3;
} finally {
console.log('Cleanup!');
}
}
const gen = withCleanup();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.return('done')); // Logs: Cleanup!
// { value: 'done', done: true }
The throw() method injects an error into the generator.
function* errorHandler() {
try {
yield 1;
yield 2;
yield 3;
} catch (error) {
console.log('Caught:', error.message);
yield 'recovered';
}
}
const gen = errorHandler();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.throw(new Error('Oops!'))); // Logs: Caught: Oops!
// { value: 'recovered', done: false }
console.log(gen.next()); // { value: undefined, done: true }
function* noErrorHandler() {
yield 1;
yield 2;
}
const gen = noErrorHandler();
gen.next(); // { value: 1, done: false }
try {
gen.throw(new Error('Boom!')); // Throws error (not caught in generator)
} catch (error) {
console.log('Caught outside:', error.message);
}
function* fileProcessor(filename) {
console.log(`Opening ${filename}`);
try {
yield 'reading';
yield 'processing';
yield 'writing';
} finally {
console.log(`Closing ${filename}`);
}
}
const processor = fileProcessor('data.txt');
console.log(processor.next().value); // Opening data.txt, returns: reading
console.log(processor.next().value); // returns: processing
processor.return('cancelled'); // Closing data.txt
function* resilientTask() {
let retries = 3;
while (retries > 0) {
try {
yield 'attempting';
yield 'success';
return 'completed';
} catch (error) {
retries--;
console.log(`Error: ${error.message}, retries left: ${retries}`);
if (retries === 0) {
throw error;
}
yield 'retrying';
}
}
}
const task = resilientTask();
task.next(); // attempting
task.throw(new Error('Failed')); // Error: Failed, retries left: 2
task.next(); // attempting
task.throw(new Error('Failed again')); // Error: Failed again, retries left: 1
task.next(); // attempting
task.next(); // success
function* lifecycle() {
console.log('Start');
try {
const a = yield 'step1';
console.log('Got:', a);
const b = yield 'step2';
console.log('Got:', b);
yield 'step3';
} catch (error) {
console.log('Error:', error.message);
yield 'error-recovery';
} finally {
console.log('Cleanup');
}
}
// Example 1: Normal flow
const gen1 = lifecycle();
gen1.next(); // Start
gen1.next('value1'); // Got: value1
gen1.next('value2'); // Got: value2
gen1.next(); // Cleanup
// Example 2: With error
const gen2 = lifecycle();
gen2.next(); // Start
gen2.throw(new Error('Something wrong')); // Error: Something wrong, Cleanup
// Example 3: Early return
const gen3 = lifecycle();
gen3.next(); // Start
gen3.return('early'); // Cleanup
next(value) - Advances generator, passes valuereturn(value) - Terminates early, runs finallythrow(error) - Injects error into generatortry/catch for error handlingfinally for cleanupNow let's explore yield delegation with yield*!