Learning Objectives

  • Master bidirectional data flow
  • Build interactive generator systems
  • Implement coroutine patterns
  • Create stateful generators

Bidirectional Communication

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

Real-World: Interactive CLI

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

Coroutine Pattern

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

State Machine with Communication

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

Data Pipeline with Feedback

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 }

Real-World: Game Loop

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

Request-Response Pattern

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, ... }

Async Task Coordinator

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 }

Key Takeaways

  • ✅ Generators enable bidirectional communication
  • ✅ Perfect for interactive systems
  • ✅ Implement state machines naturally
  • ✅ Build coroutines and pipelines
  • ✅ Great for game loops and wizards
  • ✅ Maintain state between interactions

Next Steps

Now let's explore advanced patterns like infinite sequences and async generators!