Что такое генераторы в JavaScript?
Генераторы в 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* — не понимают, зачем нужно делегирование


