Learning Objectives

  • Master different generator declaration styles
  • Understand yield expressions
  • Learn generator execution flow
  • Practice common generator patterns

Generator Declaration Styles

1. Function Declaration

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = myGenerator();
console.log(gen.next()); // { value: 1, done: false }

2. Function Expression

const myGenerator = function* () {
  yield 'a';
  yield 'b';
  yield 'c';
};

const gen = myGenerator();
console.log([...gen]); // ['a', 'b', 'c']

3. Object Method

const obj = {
  *myGenerator() {
    yield 1;
    yield 2;
  },
  
  // Alternative syntax
  myGenerator2: function* () {
    yield 3;
    yield 4;
  }
};

console.log([...obj.myGenerator()]); // [1, 2]
console.log([...obj.myGenerator2()]); // [3, 4]

4. Class Method

class MyClass {
  *instanceGenerator() {
    yield 'instance';
  }
  
  static *staticGenerator() {
    yield 'static';
  }
}

const instance = new MyClass();
console.log([...instance.instanceGenerator()]); // ['instance']
console.log([...MyClass.staticGenerator()]); // ['static']

Yield Expressions

Basic Yield

function* basicYield() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = basicYield();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

Yield with Expressions

function* yieldExpressions() {
  yield 1 + 1;
  yield Math.random();
  yield 'Hello' + ' ' + 'World';
  yield [1, 2, 3];
  yield { name: 'John', age: 30 };
}

for (const value of yieldExpressions()) {
  console.log(value);
}
// 2
// 0.123... (random)
// Hello World
// [1, 2, 3]
// { name: 'John', age: 30 }

Conditional Yield

function* conditionalYield(n) {
  for (let i = 0; i < n; i++) {
    if (i % 2 === 0) {
      yield i;
    }
  }
}

console.log([...conditionalYield(10)]); // [0, 2, 4, 6, 8]

Generator Execution Flow

function* executionFlow() {
  console.log('Start');
  
  yield 1;
  console.log('After first yield');
  
  yield 2;
  console.log('After second yield');
  
  yield 3;
  console.log('After third yield');
  
  return 'Done';
}

const gen = executionFlow();

console.log('Created generator');
console.log(gen.next()); // Logs: Start, Returns: { value: 1, done: false }
console.log(gen.next()); // Logs: After first yield, Returns: { value: 2, done: false }
console.log(gen.next()); // Logs: After second yield, Returns: { value: 3, done: false }
console.log(gen.next()); // Logs: After third yield, Returns: { value: 'Done', done: true }

Return in Generators

function* withReturn() {
  yield 1;
  yield 2;
  return 3; // Final value
  yield 4;  // Never reached
}

const gen = withReturn();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }
console.log(gen.next()); // { value: undefined, done: true }

// Note: for...of doesn't include return value
console.log([...withReturn()]); // [1, 2]

Common Patterns

1. Range Generator

function* range(start, end, step = 1) {
  for (let i = start; i <= end; i += step) {
    yield i;
  }
}

console.log([...range(1, 10)]); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log([...range(0, 20, 5)]); // [0, 5, 10, 15, 20]

2. Repeat Generator

function* repeat(value, times) {
  for (let i = 0; i < times; i++) {
    yield value;
  }
}

console.log([...repeat('hello', 3)]); // ['hello', 'hello', 'hello']
console.log([...repeat(0, 5)]); // [0, 0, 0, 0, 0]

3. Cycle Generator

function* cycle(array) {
  while (true) {
    for (const item of array) {
      yield item;
    }
  }
}

const colors = cycle(['red', 'green', 'blue']);
console.log(colors.next().value); // red
console.log(colors.next().value); // green
console.log(colors.next().value); // blue
console.log(colors.next().value); // red (cycles back)

4. Filter Generator

function* filter(iterable, predicate) {
  for (const item of iterable) {
    if (predicate(item)) {
      yield item;
    }
  }
}

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = filter(numbers, n => n % 2 === 0);

console.log([...evens]); // [2, 4, 6, 8, 10]

5. Map Generator

function* map(iterable, transform) {
  for (const item of iterable) {
    yield transform(item);
  }
}

const numbers = [1, 2, 3, 4, 5];
const doubled = map(numbers, n => n * 2);

console.log([...doubled]); // [2, 4, 6, 8, 10]

Combining Generators

function* numbers() {
  yield 1;
  yield 2;
  yield 3;
}

function* letters() {
  yield 'a';
  yield 'b';
  yield 'c';
}

function* combined() {
  yield* numbers();
  yield* letters();
}

console.log([...combined()]); // [1, 2, 3, 'a', 'b', 'c']

Real-World Example: ID Generator

function* idGenerator(prefix = 'ID') {
  let id = 1;
  
  while (true) {
    yield `${prefix}-${String(id).padStart(5, '0')}`;
    id++;
  }
}

const userIds = idGenerator('USER');
console.log(userIds.next().value); // USER-00001
console.log(userIds.next().value); // USER-00002
console.log(userIds.next().value); // USER-00003

const orderIds = idGenerator('ORDER');
console.log(orderIds.next().value); // ORDER-00001
console.log(orderIds.next().value); // ORDER-00002

Real-World Example: Pagination

function* paginate(items, pageSize) {
  for (let i = 0; i < items.length; i += pageSize) {
    yield items.slice(i, i + pageSize);
  }
}

const data = Array.from({ length: 25 }, (_, i) => i + 1);
const pages = paginate(data, 10);

console.log(pages.next().value); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(pages.next().value); // [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
console.log(pages.next().value); // [21, 22, 23, 24, 25]

Generator State

function* statefulGenerator() {
  let state = 0;
  
  while (true) {
    const input = yield state;
    
    if (input === 'increment') {
      state++;
    } else if (input === 'decrement') {
      state--;
    } else if (input === 'reset') {
      state = 0;
    }
  }
}

const gen = statefulGenerator();
console.log(gen.next().value); // 0
console.log(gen.next('increment').value); // 1
console.log(gen.next('increment').value); // 2
console.log(gen.next('decrement').value); // 1
console.log(gen.next('reset').value); // 0

Best Practices

  1. Use descriptive names - Make generator purpose clear
  2. Document infinite generators - Warn about endless loops
  3. Keep generators focused - One responsibility per generator
  4. Use yield* for delegation - Compose generators
  5. Handle cleanup - Use try/finally if needed

Key Takeaways

  • ✅ Generators use function* syntax
  • yield pauses execution and returns values
  • return ends generator (value not in iteration)
  • ✅ Generators maintain state between calls
  • ✅ Common patterns: range, filter, map, cycle
  • ✅ Perfect for lazy evaluation and infinite sequences

Next Steps

Now that you understand basic syntax, let's explore yield expressions in depth!