Что такое генераторы в JavaScript?

MiddleJavaScript · Frontend·Обновлено 4 июля 2026
Коротко
Генераторы — это специальные функции, выполнение которых можно приостанавливать и возобновлять с помощью ключевого слова yield. Они возвращают итерируемый объект-генератор, позволяя лениво производить последовательности значений.

Генераторы в JavaScript

Генераторы — это функции особого типа, объявляемые с помощью синтаксиса function*. В отличие от обычных функций, они могут приостанавливать своё выполнение в произвольной точке и возобновлять его позже, сохраняя при этом весь локальный контекст (переменные, позицию выполнения).

Как работает генератор

При вызове функции-генератора её тело не выполняется сразу — вместо этого возвращается объект-генератор, реализующий протоколы Iterator и Iterable. Выполнение начинается только при вызове метода .next() и продолжается до ближайшего yield или return.

Метод .next() возвращает объект { value, done }, где value — переданное в yield значение, а done — флаг завершения генератора.

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

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

Двусторонняя передача данных

В yield можно не только отдавать данные наружу, но и принимать значения внутрь — через аргумент .next(value). Это делает генераторы мощным инструментом для управления потоком данных.

Делегирование: yield*

С помощью yield* генератор может делегировать выполнение другому итерируемому объекту или генератору:

function* inner() {
  yield 'a';
  yield 'b';
}

function* outer() {
  yield 1;
  yield* inner(); // делегируем выполнение
  yield 2;
}

console.log([...outer()]); // [1, 'a', 'b', 2]

Бесконечные последовательности

Благодаря ленивой природе генераторы отлично подходят для создания бесконечных или очень больших последовательностей без нагрузки на память:

function* naturals() {
  let n = 1;
  while (true) {
    yield n++;
  }
}

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

Генераторы и асинхронность

До появления async/await генераторы использовались для управления асинхронным кодом (библиотеки co, redux-saga). Связка генератора с промисами позволяла писать асинхронный код в синхронном стиле. Сегодня async/await — синтаксический сахар над тем же механизмом.

redux-saga до сих пор активно использует генераторы, так как они дают тонкий контроль над эффектами и делают логику легко тестируемой.

Управление генератором

Помимо .next(), генератор поддерживает:

  • .return(value) — принудительно завершает генератор
  • .throw(error) — бросает ошибку внутрь генератора, которую можно поймать через try/catch

Когда применять генераторы

  • Ленивая обработка больших или бесконечных наборов данных
  • Кастомные итераторы для структур данных
  • Управление сложным асинхронным потоком (redux-saga, co)
  • Конечные автоматы с явным состоянием

Что хочет услышать интервьюер

Кандидат знает синтаксис function* и ключевое слово yield, понимает, что генератор возвращает итератор

Понимание протокола итератора: метод .next() возвращает { value, done }

Осознание ленивости вычислений — тело не выполняется до вызова .next()

Знание практических применений: бесконечные последовательности, кастомные итераторы, асинхронность (redux-saga, co)

Понимание двусторонней передачи данных через yield и аргумент .next(value)

Пример: Базовый генератор и протокол итератора

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

// Использование в for...of (протокол Iterable)
for (const num of range(0, 10, 2)) {
  console.log(num); // 0, 2, 4, 6, 8
}

// Ручное управление через .next()
const gen = range(1, 4);
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

Пример: Двусторонняя передача данных

function* dialog(): Generator<string, void, string> {
  // yield отдаёт вопрос наружу и принимает ответ внутрь
  const name = yield 'Как тебя зовут?';
  const age = yield `Привет, ${name}! Сколько тебе лет?`;
  console.log(`${name}, ${age} лет — записан!`);
}

const conv = dialog();
console.log(conv.next().value);         // 'Как тебя зовут?'
console.log(conv.next('Алексей').value); // 'Привет, Алексей! Сколько тебе лет?'
conv.next('28');                         // 'Алексей, 28 лет — записан!'

Пример: Бесконечная последовательность Фибоначчи

function* fibonacci(): Generator<number> {
  let [prev, curr] = [0, 1];
  while (true) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}

// Берём первые 7 чисел без загрузки всей последовательности в память
function take<T>(gen: Generator<T>, n: number): T[] {
  const result: T[] = [];
  for (const val of gen) {
    result.push(val);
    if (result.length >= n) break;
  }
  return result;
}

console.log(take(fibonacci(), 7)); // [1, 1, 2, 3, 5, 8, 13]

Пример: Обработка ошибок внутри генератора

function* safeGen(): Generator<number, string, number> {
  try {
    const x = yield 1;
    const y = yield x * 2;
    return `Результат: ${y}`;
  } catch (err) {
    console.error('Поймали ошибку внутри генератора:', err);
    return 'Завершено с ошибкой';
  }
}

const g = safeGen();
g.next();          // запускаем, получаем { value: 1, done: false }
g.next(10);        // передаём 10, получаем { value: 20, done: false }
g.throw(new Error('Что-то пошло не так')); // { value: 'Завершено с ошибкой', done: true }

Типичные ошибки

Путают генераторы с обычными функциями — не понимают, что вызов function*() не запускает тело, а лишь создаёт объект-генератор

Забывают, что после return (или когда yield-ов больше нет) done становится true и повторные вызовы .next() возвращают { value: undefined, done: true }

Не знают о двусторонней передаче данных — думают, что yield только отдаёт значения наружу

Не могут объяснить связь генераторов с async/await и называют их устаревшими без понимания контекста (redux-saga)

Смешивают yield и yield* — не понимают, зачем нужно делегирование

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.5
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее