JavaScript Generators Tutorial - Section 2: Fundamentals
Generators enable two-way communication: they can yield values out and receive values in.
function* conversation() {
const name = yield 'What is your name?';
const age = yield `Hello ${name}, how old are you?`;
return `${name} is ${age} years old`;
}
const chat = conversation();
console.log(chat.next().value); // What is your name?
console.log(chat.next('Alice').value); // Hello Alice, how old are you?
console.log(chat.next(30).value); // Alice is 30 years old
function* cliWizard() {
console.log('Welcome to the setup wizard!');
const projectName = yield 'Enter project name:';
const framework = yield 'Choose framework (react/vue/angular):';
const typescript = yield 'Use TypeScript? (yes/no):';
return {
projectName,
framework,
typescript: typescript === 'yes',
created: new Date()
};
}
const wizard = cliWizard();
wizard.next(); // Welcome message
wizard.next('my-app'); // Enter project name
wizard.next('react'); // Choose framework
const config = wizard.next('yes').value; // Final config
function* producer() {
let id = 1;
while (true) {
const command = yield { id: id++, status: 'ready' };
if (command === 'stop') break;
}
}
function* consumer(gen) {
let result = gen.next();
while (!result.done) {
console.log('Processing:', result.value);
result = gen.next(Math.random() > 0.8 ? 'stop' : 'continue');
}
}
const prod = producer();
const cons = consumer(prod);
[...cons]; // Processes until random stop
function* stateMachine() {
let state = 'idle';
while (true) {
const action = yield state;
switch (state) {
case 'idle':
if (action === 'start') state = 'running';
break;
case 'running':
if (action === 'pause') state = 'paused';
if (action === 'stop') state = 'idle';
break;
case 'paused':
if (action === 'resume') state = 'running';
if (action === 'stop') state = 'idle';
break;
}
}
}
const machine = stateMachine();
console.log(machine.next().value); // idle
console.log(machine.next('start').value); // running
console.log(machine.next('pause').value); // paused
console.log(machine.next('resume').value); // running
console.log(machine.next('stop').value); // idle
function* dataProcessor() {
let processed = 0;
let errors = 0;
while (true) {
const data = yield { processed, errors };
try {
// Simulate processing
if (data && data.value > 0) {
processed++;
} else {
throw new Error('Invalid data');
}
} catch (error) {
errors++;
}
}
}
const processor = dataProcessor();
processor.next(); // Initialize
console.log(processor.next({ value: 10 }).value); // { processed: 1, errors: 0 }
console.log(processor.next({ value: -1 }).value); // { processed: 1, errors: 1 }
console.log(processor.next({ value: 20 }).value); // { processed: 2, errors: 1 }
function* gameLoop() {
let player = { x: 0, y: 0, health: 100 };
while (player.health > 0) {
const input = yield { player, message: 'Your move' };
// Process input
if (input === 'up') player.y++;
if (input === 'down') player.y--;
if (input === 'left') player.x--;
if (input === 'right') player.x++;
if (input === 'damage') player.health -= 10;
}
return { player, message: 'Game Over' };
}
const game = gameLoop();
game.next(); // Start
game.next('right'); // Move right
game.next('up'); // Move up
game.next('damage'); // Take damage
// Continue until health reaches 0
function* apiHandler() {
while (true) {
const request = yield 'waiting';
if (request.method === 'GET') {
yield { status: 200, data: { id: 1, name: 'Item' } };
} else if (request.method === 'POST') {
yield { status: 201, data: { created: true } };
} else {
yield { status: 405, error: 'Method not allowed' };
}
}
}
const api = apiHandler();
api.next(); // Start
console.log(api.next({ method: 'GET' }).value); // { status: 200, ... }
api.next(); // Continue
console.log(api.next({ method: 'POST' }).value); // { status: 201, ... }
function* taskCoordinator() {
const tasks = [];
while (true) {
const command = yield {
pending: tasks.filter(t => !t.done).length,
completed: tasks.filter(t => t.done).length
};
if (command.action === 'add') {
tasks.push({ id: tasks.length + 1, done: false });
} else if (command.action === 'complete') {
const task = tasks.find(t => t.id === command.id);
if (task) task.done = true;
} else if (command.action === 'clear') {
tasks.length = 0;
}
}
}
const coordinator = taskCoordinator();
coordinator.next(); // Initialize
coordinator.next({ action: 'add' }); // Add task 1
coordinator.next({ action: 'add' }); // Add task 2
console.log(coordinator.next({ action: 'complete', id: 1 }).value);
// { pending: 1, completed: 1 }
Now let's explore advanced patterns like infinite sequences and async generators!