Learning Objectives

  • Understand yield as an expression
  • Learn two-way communication with generators
  • Master passing values to generators
  • Explore practical yield patterns

Yield as an Expression

The yield keyword is not just a statement - it's an expression that can receive values from the caller.

function* twoWay() {
  const x = yield 'First';
  console.log('Received:', x);
  
  const y = yield 'Second';
  console.log('Received:', y);
  
  return 'Done';
}

const gen = twoWay();

console.log(gen.next());      // { value: 'First', done: false }
console.log(gen.next(10));    // Logs: Received: 10
                              // { value: 'Second', done: false }
console.log(gen.next(20));    // Logs: Received: 20
                              // { value: 'Done', done: true }

How Yield Communication Works

function* communicator() {
  console.log('Started');
  
  const a = yield 1;
  console.log('Got a:', a);
  
  const b = yield 2;
  console.log('Got b:', b);
  
  const c = yield 3;
  console.log('Got c:', c);
}

const gen = communicator();

// First next() starts the generator
console.log(gen.next());        // Logs: Started
                                // Returns: { value: 1, done: false }

// Second next() sends value to first yield
console.log(gen.next('hello')); // Logs: Got a: hello
                                // Returns: { value: 2, done: false }

// Third next() sends value to second yield
console.log(gen.next('world')); // Logs: Got b: world
                                // Returns: { value: 3, done: false }

console.log(gen.next('!')); // Logs: Got c: !
                            // Returns: { value: undefined, done: true }

Practical Example: State Machine

function* trafficLight() {
  while (true) {
    console.log('🔴 Red');
    const redDuration = yield 'red';
    
    console.log('🟢 Green');
    const greenDuration = yield 'green';
    
    console.log('🟡 Yellow');
    const yellowDuration = yield 'yellow';
  }
}

const light = trafficLight();

light.next();           // 🔴 Red
light.next(3000);       // 🟢 Green (red was 3s)
light.next(5000);       // 🟡 Yellow (green was 5s)
light.next(1000);       // 🔴 Red (yellow was 1s)

Calculator Example

function* calculator() {
  let result = 0;
  
  while (true) {
    const operation = yield result;
    
    if (!operation) continue;
    
    const [op, value] = operation.split(' ');
    const num = parseFloat(value);
    
    switch (op) {
      case 'add':
        result += num;
        break;
      case 'subtract':
        result -= num;
        break;
      case 'multiply':
        result *= num;
        break;
      case 'divide':
        result /= num;
        break;
      case 'reset':
        result = 0;
        break;
    }
  }
}

const calc = calculator();
calc.next();                    // Start: { value: 0, done: false }
console.log(calc.next('add 10').value);      // 10
console.log(calc.next('multiply 2').value);  // 20
console.log(calc.next('subtract 5').value);  // 15
console.log(calc.next('divide 3').value);    // 5
console.log(calc.next('reset').value);       // 0

Yield with Default Values

function* withDefaults() {
  const a = (yield 'Enter A') || 'default A';
  console.log('A:', a);
  
  const b = (yield 'Enter B') || 'default B';
  console.log('B:', b);
}

const gen = withDefaults();
gen.next();           // { value: 'Enter A', done: false }
gen.next();           // Logs: A: default A (undefined || 'default A')
gen.next('custom');   // Logs: B: custom

Accumulator Pattern

function* accumulator(initial = 0) {
  let total = initial;
  
  while (true) {
    const value = yield total;
    if (value !== undefined) {
      total += value;
    }
  }
}

const sum = accumulator(0);
console.log(sum.next().value);      // 0
console.log(sum.next(5).value);     // 5
console.log(sum.next(10).value);    // 15
console.log(sum.next(3).value);     // 18
console.log(sum.next().value);      // 18 (no change)

Query-Response Pattern

function* dataProcessor() {
  while (true) {
    const query = yield 'Ready';
    
    if (query === 'status') {
      yield 'Processing...';
    } else if (query === 'data') {
      yield { items: [1, 2, 3], count: 3 };
    } else if (query === 'error') {
      yield new Error('Something went wrong');
    } else {
      yield 'Unknown command';
    }
  }
}

const processor = dataProcessor();
processor.next();                    // Start
console.log(processor.next('status').value);  // 'Processing...'
processor.next();                    // Continue
console.log(processor.next('data').value);    // { items: [1, 2, 3], count: 3 }
processor.next();                    // Continue
console.log(processor.next('error').value);   // Error: Something went wrong

Real-World: Form Wizard

function* formWizard() {
  const name = yield { step: 1, question: 'What is your name?' };
  console.log('Name:', name);
  
  const email = yield { step: 2, question: 'What is your email?' };
  console.log('Email:', email);
  
  const age = yield { step: 3, question: 'What is your age?' };
  console.log('Age:', age);
  
  return {
    name,
    email,
    age,
    completed: true
  };
}

const wizard = formWizard();

console.log(wizard.next().value);
// { step: 1, question: 'What is your name?' }

console.log(wizard.next('John Doe').value);
// Logs: Name: John Doe
// { step: 2, question: 'What is your email?' }

console.log(wizard.next('john@example.com').value);
// Logs: Email: john@example.com
// { step: 3, question: 'What is your age?' }

console.log(wizard.next(30).value);
// Logs: Age: 30
// { name: 'John Doe', email: 'john@example.com', age: 30, completed: true }

Validation Pattern

function* validator() {
  while (true) {
    const input = yield 'Enter value';
    
    if (typeof input === 'number' && input > 0) {
      yield { valid: true, value: input };
    } else {
      yield { valid: false, error: 'Must be a positive number' };
    }
  }
}

const val = validator();
val.next();                          // Start
console.log(val.next(10).value);     // { valid: true, value: 10 }
val.next();                          // Continue
console.log(val.next(-5).value);     // { valid: false, error: '...' }
val.next();                          // Continue
console.log(val.next('abc').value);  // { valid: false, error: '...' }

Key Takeaways

  • yield is an expression that can receive values
  • ✅ Use next(value) to send values into generator
  • ✅ First next() starts generator, can't pass value
  • ✅ Perfect for state machines and wizards
  • ✅ Enables two-way communication
  • ✅ Great for interactive processes

Next Steps

Now let's explore generator methods like return() and throw()!