Урок по треку JavaScript 7 мин

Итераторы и генераторы за 7 минут

Протокол итерации, function*, yield, ленивые последовательности и for await.

for...of, spread, деструктуризация массива, Array.from, new Map(entries), Promise.all работают с любым итерируемым объектом, а не только с массивами. За этим стоит простой протокол, который можно реализовать самому, а генераторы делают это в пару строк.

Протокол итерации

Итератор это объект с методом next(), который возвращает { value, done }. Когда done: true, перебор закончен.

Итерируемый объект (iterable) это объект с методом [Symbol.iterator](), который возвращает итератор.

const range = {
  from: 1,
  to: 3,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next: () => current <= last
        ? { value: current++, done: false }
        : { value: undefined, done: true },
    };
  },
};

[...range];               // [1, 2, 3]
for (const n of range) {} // 1, 2, 3

Встроенные итерируемые: массивы, строки (по символам Unicode, а не по UTF-16 единицам), Map, Set, arguments, NodeList, типизированные массивы. Обычный объект {} не итерируемый: for...of по нему бросит TypeError, используйте Object.entries(obj).

Метод return() у итератора необязателен: for...of вызывает его при досрочном выходе (break, return, исключение), чтобы итератор мог освободить ресурсы.

Генераторы

Функция-генератор function* при вызове не выполняет тело, а возвращает объект-генератор. Он одновременно итератор и итерируемый. Каждый next() выполняет тело до следующего yield и приостанавливается.

function* idGenerator() {
  let id = 1;
  while (true) yield id++;
}
const ids = idGenerator();
ids.next().value; // 1
ids.next().value; // 2

Бесконечный цикл тут безопасен: код выполняется только по запросу. Это ленивые вычисления.

С генератором range выше превращается в:

const range = {
  from: 1, to: 3,
  *[Symbol.iterator]() {
    for (let i = this.from; i <= this.to; i++) yield i;
  },
};

Двусторонняя связь

  • gen.next(value): значение становится результатом выражения yield внутри генератора. Первый next() только запускает генератор до первого yield, его аргумент теряется.
  • gen.return(value): завершает генератор, при этом выполняются блоки finally.
  • gen.throw(error): бросает ошибку в точке yield, её можно поймать try/catch внутри генератора.
  • return x внутри генератора даёт { value: x, done: true }, но for...of и spread это значение игнорируют.

yield*

yield* делегирует перебор другому итерируемому:

function* walk(tree) {
  yield tree.value;
  for (const child of tree.children) yield* walk(child);
}

Обход дерева, плоский список из вложенного, склейка последовательностей.

Асинхронная итерация

Асинхронный итератор возвращает из next() промис. Асинхронный генератор async function* может использовать и await, и yield. Перебирают их через for await...of.

async function* pages(url) {
  while (url) {
    const res = await fetch(url).then((r) => r.json());
    yield res.items;
    url = res.next;
  }
}
for await (const items of pages('/api/orders')) render(items);

Так удобно читать постраничные API, потоки (ReadableStream и потоки Node итерируемы асинхронно) и события. Array.fromAsync (ES2024) собирает асинхронный итерируемый в массив.

Iterator helpers (ES2025)

У итераторов появились ленивые методы: map, filter, take, drop, flatMap, reduce, toArray, some, every, find, forEach. Они работают поштучно, без промежуточных массивов: ids.filter(isEven).take(5).toArray(). Поддержка: Node 22, Chrome, Firefox, Safari 18.4+.

Где это на практике

  • Ленивые последовательности и бесконечные генераторы id.
  • Обход деревьев и графов без рекурсивного накопления массивов.
  • Постраничная загрузка и потоки данных через for await.
  • Redux-Saga построена на генераторах.
  • До async/await генераторы с библиотекой co использовали для асинхронного кода. async/await по сути и есть генератор с автоматическим запуском.

Частые ошибки

  • for...of по обычному объекту.
  • Ожидание, что вызов генератора выполнит его тело.
  • Повторный перебор уже исчерпанного генератора: он одноразовый.
  • Передача значения в первый next().

Что спросят на собесе

  • Что такое итератор и итерируемый объект, как сделать объект итерируемым.
  • Что такое генератор и чем yield отличается от return.
  • Как передать значение в генератор, что делает yield*.
  • Чем for...of отличается от for...in.
  • Что такое асинхронные итераторы и for await.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.