Генераторы #
Генератор — особая разновидность итератора: функция function* при вызове возвращает объект-генератор с методами next, return и throw. Ключевое слово yield приостанавливает выполнение и отдаёт значение наружу. Генераторы удобны для ленивых потоков данных — постраничная загрузка товаров, обход вложенных категорий, пошаговая сборка чекаута.
Объявление и первый next()
#
function* orderIdSequence(prefix) {
let n = 1
while (true) {
yield `${prefix}-${String(n).padStart(4, '0')}`
n++
}
}
const ids = orderIdSequence('ord')
ids.next() // { value: 'ord-0001', done: false }
ids.next() // { value: 'ord-0002', done: false }
Тело генератора не выполняется при вызове orderIdSequence('ord') — только при первом next().
yield и возобновление
#
Выражение после yield становится value в результате next(). Аргумент следующего next(arg) попадает в генератор как результат предыдущего yield:
function* checkoutDialog() {
const name = yield 'Введите имя получателя'
const phone = yield `Спасибо, ${name}. Телефон?`
yield `Заказ оформлен для ${name}, ${phone}`
}
const dialog = checkoutDialog()
dialog.next() // { value: 'Введите имя получателя', done: false }
dialog.next('Анна') // { value: 'Спасибо, Анна. Телефон?', done: false }
dialog.next('+79991112233') // { value: 'Заказ оформлен для Анна, +79991112233', done: false }
dialog.next() // { value: undefined, done: true }
Двусторонняя передача данных через yield / next(arg) встречается реже, чем простая генерация последовательности.
return и завершение
#
return внутри генератора завершает итерацию:
function* limitedOffers(offers, max) {
for (let i = 0; i < offers.length; i++) {
if (i >= max) return
yield offers[i]
}
}
[...limitedOffers(['-10%', '-15%', 'бесплатная доставка'], 2)]
// ["-10%", "-15%"]
Вызов generator.return(value) снаружи тоже завершает генератор с указанным value и done: true.
Делегирование yield*
#
Передаёт итерацию другому iterable или генератору:
function* trackIds(primary, extras) {
yield* primary
yield* extras
}
const playlist = trackIds(
['t-01', 't-02'],
['bonus-1']
)
[...playlist] // ["t-01", "t-02", "bonus-1"]
yield* inner() эквивалентно развёртыванию всех значений inner в текущий генератор.
Генератор как iterable #
Объект-генератор уже реализует [Symbol.iterator]() — возвращает this:
function* deliveryWindows() {
yield '10:00–14:00'
yield '14:00–18:00'
yield '18:00–22:00'
}
for (const slot of deliveryWindows()) {
console.log(slot)
}
Ленивая фильтрация корзины #
function* inStockOnly(items) {
for (const item of items) {
if (item.stock > 0) yield item
}
}
const catalog = [
{ sku: 'KB-01', stock: 3 },
{ sku: 'HD-07', stock: 0 },
{ sku: 'MS-03', stock: 12 },
]
for (const item of inStockOnly(catalog)) {
console.log(item.sku)
}
// KB-01, MS-03
Элементы с нулевым остатком не обрабатываются — вычисление откладывается до запроса следующего next().
Обход дерева категорий #
function* walkCategories(node) {
yield node.name
for (const child of node.children ?? []) {
yield* walkCategories(child)
}
}
const tree = {
name: 'Каталог',
children: [
{ name: 'Клавиатуры', children: [] },
{ name: 'Аудио', children: [{ name: 'Наушники', children: [] }] },
],
}
[...walkCategories(tree)]
// ["Каталог", "Клавиатуры", "Аудио", "Наушники"]
throw в генератор
#
generator.throw(err) возобновляет выполнение с выброшенной ошибкой в точке yield; если генератор её не перехватит, итерация завершится:
function* fragileStep() {
try {
yield 'шаг 1'
yield 'шаг 2'
} catch (e) {
yield `ошибка: ${e.message}`
}
}
const g = fragileStep()
g.next()
g.throw(new Error('сеть')) // { value: 'ошибка: сеть', done: false }
Практика: пагинация API каталога #
async function fetchPage(page) {
// имитация ответа
const batch = [
{ id: page * 10, title: `Товар ${page * 10}` },
{ id: page * 10 + 1, title: `Товар ${page * 10 + 1}` },
]
return page < 3 ? batch : []
}
function* paginatedCatalog(fetchPage) {
let page = 0
while (true) {
const batch = fetchPage(page) // синхронная заглушка; для API — async-генератор
if (!batch.length) return
yield* batch
page++
}
}
// Для реального fetch используйте async function* — см. Асинхронные итераторы
Итог #
function*возвращает генератор; код выполняется лениво, по вызовамnext().yieldотдаёт значение;yield*делегирует другому iterable.- Генератор совместим с
for...ofи spread — это iterable с одноразовым проходом. returnиthrowзавершают или прерывают итерацию с контролируемым результатом.