84. Генераторы и итераторы
Генераторы — функции, которые могут приостанавливать своё выполнение и возобновлять его позже. Вместе с протоколом итерации они позволяют создавать ленивые последовательности и управлять потоком выполнения нестандартными способами.
Протокол итерации
Заголовок раздела «Протокол итерации»JavaScript использует два протокола для итерации:
Итерируемый (Iterable) — объект с методом [Symbol.iterator](), который возвращает итератор.
Итератор (Iterator) — объект с методом next(), возвращающим { value, done }.
// Ручная реализация итератораfunction createCounter(start, end) { let current = start return { // Сам является итерируемым (возвращает себя) [Symbol.iterator]() { return this }, next() { return current <= end ? { value: current++, done: false } : { value: undefined, done: true } } }}
const counter = createCounter(1, 3)console.log(counter.next()) // { value: 1, done: false }console.log(counter.next()) // { value: 2, done: false }console.log(counter.next()) // { value: 3, done: false }console.log(counter.next()) // { value: undefined, done: true }
for (const n of createCounter(1, 5)) { console.log(n) // 1 2 3 4 5}Генераторы: function*
Заголовок раздела «Генераторы: function*»Генератор — специальная функция с *. Оператор yield приостанавливает выполнение:
function* simpleGenerator() { console.log('Шаг 1') yield 10 console.log('Шаг 2') yield 20 console.log('Шаг 3') return 30}
const gen = simpleGenerator()
console.log(gen.next()) // Шаг 1 → { value: 10, done: false }console.log(gen.next()) // Шаг 2 → { value: 20, done: false }console.log(gen.next()) // Шаг 3 → { value: 30, done: true }console.log(gen.next()) // { value: undefined, done: true }Генераторы автоматически реализуют оба протокола — их можно использовать в for...of:
function* range(from, to, step = 1) { for (let i = from; i <= to; i += step) { yield i }}
console.log([...range(1, 10, 2)]) // [1, 3, 5, 7, 9]console.log([...range(0, 100, 25)]) // [0, 25, 50, 75, 100]yield* — делегирование
Заголовок раздела «yield* — делегирование»yield* передаёт управление другому итерируемому объекту:
function* numbers() { yield 1; yield 2 }function* letters() { yield 'a'; yield 'b' }
function* combined() { yield* numbers() // делегирует генератор numbers yield* letters() // делегирует генератор letters yield* [3, 4] // делегирует массив}
console.log([...combined()]) // [1, 2, 'a', 'b', 3, 4]Передача данных в генератор через next()
Заголовок раздела «Передача данных в генератор через next()»gen.next(value) передаёт значение внутрь генератора — оно становится результатом yield:
function* calculator() { let result = 0 while (true) { const input = yield result // получаем входные данные if (input === null) break result += input } return result}
const calc = calculator()calc.next() // запуск генератораcalc.next(10) // передаём 10calc.next(20) // передаём 20calc.next(5) // передаём 5const { value } = calc.next(null) // завершаемconsole.log(value) // 35Бесконечные генераторы
Заголовок раздела «Бесконечные генераторы»Генераторы идеальны для бесконечных последовательностей — они ленивые и не вычисляют всё заранее:
function* fibonacci() { let [a, b] = [0, 1] while (true) { yield a; [a, b] = [b, a + b] }}
function take(gen, n) { const result = [] for (const val of gen) { result.push(val) if (result.length >= n) break } return result}
console.log(take(fibonacci(), 10))// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
// Бесконечный счётчикfunction* naturals(start = 1) { while (true) yield start++}
const first5Odds = [...take(naturals(), 100)].filter(n => n % 2 !== 0).slice(0, 5)console.log(first5Odds) // [1, 3, 5, 7, 9]gen.throw() и gen.return()
Заголовок раздела «gen.throw() и gen.return()»function* safeGen() { try { yield 1 yield 2 yield 3 } catch (err) { console.log('Поймана ошибка:', err.message) yield 'после ошибки' }}
const g = safeGen()console.log(g.next()) // { value: 1, done: false }console.log(g.throw(new Error('упс!')))// Поймана ошибка: упс!// { value: 'после ошибки', done: false }
// gen.return() завершает генератор досрочноconst g2 = safeGen()g2.next()console.log(g2.return('стоп')) // { value: 'стоп', done: true }Практический паттерн: пагинация
Заголовок раздела «Практический паттерн: пагинация»async function* paginatedFetch(url) { let page = 1 while (true) { const response = await fetch(`${url}?page=${page}`) const data = await response.json() if (data.items.length === 0) break yield* data.items page++ }}
// Использованиеfor await (const item of paginatedFetch('/api/users')) { console.log(item) // Данные загружаются по мере необходимости}Задания для практики
Заголовок раздела «Задания для практики»- Реализуйте генератор
permutations(arr)для всех перестановок массива. - Создайте генератор
flatten(arr)для рекурсивного развёртывания вложенных массивов. - Напишите генератор
zip(...iterables)— аналог Pythonzip(). - Реализуйте конечный автомат (state machine) через генератор.