JS генераторы
Генератор — специальная функция (
function*), выполнение которой можно приостанавливать и возобновлять через ключевое словоyield, возвращающая объект-итератор.
Зачем нужно
Генераторы позволяют создавать ленивые (lazy) последовательности данных — вычислять значения по одному по мере необходимости, не загружая память целиком. Они упрощают написание кастомных итераторов: вместо ручного управления состоянием достаточно использовать yield. Генераторы лежат в основе библиотек для управления побочными эффектами (redux-saga) и удобны для описания конечных автоматов.
Где используется
- Ленивые бесконечные последовательности: числа Фибоначчи, генераторы ID, пагинация
- Кастомные итераторы с минимальным кодом
- Управление асинхронным потоком (до async/await: библиотека co)
- Конечные автоматы и протоколы обмена (двунаправленная передача через
yield) - redux-saga: описание side-эффектов через генераторы
Основной контент
Базовый синтаксис
function* simpleGen {
yield 1;
yield 2;
yield 3;
}
const gen = simpleGen;
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 }
// Генератор — итерируемый объект
for (const val of simpleGen) {
console.log(val); // 1, 2, 3
}
console.log([...simpleGen]); // [1, 2, 3]
Бесконечная последовательность
function* fibonacci {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
function take(n, iterable) {
const result = ;
for (const val of iterable) {
result.push(val);
if (result.length === n) break;
}
return result;
}
console.log(take(8, fibonacci)); // [0, 1, 1, 2, 3, 5, 8, 13]
Генератор уникальных ID
function* idGenerator(prefix = 'id') {
let id = 1;
while (true) {
yield `${prefix}-${id++}`;
}
}
const userIds = idGenerator('user');
console.log(userIds.next().value); // "user-1"
console.log(userIds.next().value); // "user-2"
Передача значений в генератор через next
function* calculator {
let result = 0;
while (true) {
const input = yield result;
if (input === null) break;
result += input;
}
return result;
}
const calc = calculator;
calc.next(); // запускаем, первый yield, result = 0
calc.next(10); // передаём 10, result = 10
calc.next(20); // передаём 20, result = 30
console.log(calc.next(null)); // { value: 30, done: true }
yield* — делегирование другому генератору
function* inner {
yield 'a';
yield 'b';
}
function* outer {
yield 1;
yield* inner; // делегируем
yield 2;
}
console.log([...outer]); // [1, 'a', 'b', 2]
return и throw
function* gen {
try {
yield 1;
yield 2;
} catch (e) {
console.log('Поймали:', e.message);
yield 'error handled';
}
}
const g = gen;
console.log(g.next()); // { value: 1, done: false }
console.log(g.throw(new Error('Oops'))); // "Поймали: Oops", { value: 'error handled', done: false }
console.log(g.return('done')); // { value: 'done', done: true }
Частые ошибки
- Повторное использование генератора: после завершения (done: true) генератор нельзя «перезапустить» — нужно создать новый вызовом функции-генератора.
- Стрелочные функции не могут быть генераторами:
const gen = () => { yield 1; }— SyntaxError. - Забыть
yield*при делегировании:yield innerвернёт объект-генератор, а не значения из него. - Первый
nextне принимает аргумент: первый вызовnext(value)— значениеvalueигнорируется, так как генератор ещё не достиг ни одногоyield.
Связанные темы
Ресурсы
🎓 Источник: Генераторы и асинхронные генераторы в JavaScript
- 📅 2019-03-05 · YouTube
- Тезисы:
typeof generatorFn === 'function'— снаружи генератор не отличить от обычной функции, нужна проверка через прототип.- Существует скрытый класс
GeneratorFunction(доступен черезObject.getPrototypeOf(genFn).constructor). - Контекст генератора живёт как замыкание — все локальные переменные и шаги выполнения сохраняются между
yield. returnв генераторе →{ value, done: true };yield→{ value, done: false }.- После
done: trueможно повторно вызыватьnext— не упадёт, вернёт{ value: undefined, done: true }. yield*делегирует ЛЮБОМУ iterable (массив, строка, другой генератор).returnиthrowредко применимы на практике, но полезны для отмены/инжекции ошибок в корутину.
- Цитата:
«Генератор — это функция, чьё выполнение можно ставить на паузу. Контекст хранится как замыкание, и при каждом next мы возвращаемся в ту же точку с теми же переменными.»
🎓 Источник: Итераторы и асинхронные итераторы в JavaScript
- 📅 2019-03-05 · YouTube
- Тезисы:
- Контракт iterator = объект с
next, возвращающий{ value, done }. - Контракт iterable = объект с
[Symbol.iterator], возвращающий iterator. - Массив, строка, Map, Set реализуют iterable — поэтому работают с
for-ofи spread. - Генератор УПРОЩАЕТ ручную реализацию iterable — императивный класс vs декларативный генератор.
- Итератор замкнут на контекст: пока есть ссылка на iterator, его внутренние переменные не собираются GC.
- Контракт iterator = объект с
🎓 Источник: Архив 2018 - Часть 8 Типизированные массивы, итераторы, генераторы
- 📅 2020-01-06 · YouTube
- Тезис: генераторы вокруг типизированных массивов и буферов позволяют обрабатывать большие бинарные данные ленивыми порциями без копирования.