Learning Book

Практические применения

Практические применения генераторов

Генераторы особенно полезны там, где нужны ленивые вычисления (вычисляем только то, что нужно прямо сейчас) или бесконечные последовательности.

Диапазоны и шаги

// range(1, 10, 2) → 1, 3, 5, 7, 9
function* range(start, end, step = 1) {
  for (let i = start; i < end; i += step) {
    yield i
  }
}

console.log([...range(0, 10, 3)]) // [0, 3, 6, 9]

// Бесконечная последовательность Фибоначчи
function* fibonacci() {
  let [a, b] = [0, 1]
  while (true) {
    yield a
    ;[a, b] = [b, a + b]
  }
}

// Берём только первые N значений
function take(gen, n) {
  const result = []
  for (const val of gen) {
    result.push(val)
    if (result.length >= n) break
  }
  return result
}

take(fibonacci(), 8) // [0, 1, 1, 2, 3, 5, 8, 13]

Ленивые map и filter

В отличие от Array.map().filter(), генераторы не создают промежуточные массивы:

function* lazyMap(iterable, fn) {
  for (const item of iterable) {
    yield fn(item)
  }
}

function* lazyFilter(iterable, pred) {
  for (const item of iterable) {
    if (pred(item)) yield item
  }
}

// Обрабатываем миллион чисел без создания промежуточных массивов
const numbers = range(1, 1_000_000)
const result = lazyFilter(
  lazyMap(numbers, x => x * x),
  x => x % 3 === 0
)

// Вычисления происходят лениво — только при итерации
for (const n of result) {
  if (n > 100) break // обработали только нужное количество элементов
}
Ленивые генераторы особенно полезны при работе с большими файлами, потоками данных или бесконечными последовательностями — они не держат всё в памяти.

Конечный автомат на генераторах

// Светофор как генератор
function* trafficLight() {
  while (true) {
    yield 'green'   // едем
    yield 'yellow'  // замедляемся
    yield 'red'     // стоим
  }
}

const light = trafficLight()
light.next().value // 'green'
light.next().value // 'yellow'
light.next().value // 'red'
light.next().value // 'green' — цикл повторяется

Плоское разворачивание вложенных структур

// Рекурсивный обход дерева
function* flatten(arr) {
  for (const item of arr) {
    if (Array.isArray(item)) {
      yield* flatten(item) // рекурсивное делегирование
    } else {
      yield item
    }
  }
}

const nested = [1, [2, [3, [4]], 5], 6]
console.log([...flatten(nested)]) // [1, 2, 3, 4, 5, 6]