Итераторы и генераторы

↑ 2.1 JavaScript: продвинутый · ← Предыдущая · → Следующая

Обязательно~3 мин чтенияУровень: middle

Зачем это на собесе

Лежит в основе for...of, spread, async/await; спрашивают, как сделать объект итерируемым.

Объяснение

Итерируемый объект имеет метод [Symbol.iterator](), возвращающий итератор с методом next() → { value, done }.

const range = {
  from: 1, to: 3,
  [Symbol.iterator]() {
    let cur = this.from, end = this.to;
    return { next: () => cur <= end ? { value: cur++, done: false } : { value: undefined, done: true } };
  },
};
[...range];        // [1, 2, 3]
for (const x of range) console.log(x);

Генератор — функция с function*, приостанавливаемая yield; возвращает итератор.

function* range(a, b) { for (let i = a; i <= b; i++) yield i; }
function* ids() { let id = 1; while (true) yield id++; }        // бесконечная последовательность, ленивость
const [a, b] = ids();                                            // берём нужное
 
function* dialog() {
  const name = yield "Как вас зовут?";       // yield принимает значение из next(value)
  return `Привет, ${name}`;
}
const g = dialog(); g.next(); g.next("Анна");   // { value: "Привет, Анна", done: true }
yield* otherGenerator();                          // делегирование

Асинхронные:

async function* pages(url) { let next = url; while (next) { const r = await (await fetch(next)).json(); yield r.items; next = r.next; } }
for await (const items of pages("/api?page=1")) render(items);

Применения: ленивые последовательности, пагинация, пошаговые процессы, корутины (redux-saga), потоки данных (ReadableStream — async-итерируемый), кастомная итерация структур данных (деревья).

async/await исторически реализовывали на генераторах + Promise.

Нюансы и подводные камни

  • Генератор одноразовый: после done: true повторный обход даёт пустой результат.
  • return() и throw() на итераторе позволяют завершать/прерывать (break в for...of вызывает return).
  • Деструктуризация итерируемого потребляет его.
  • Ленивые вычисления не выполняются, пока не запрошены.
  • Итераторы-помощники (Iterator.prototype.map/filter/take) появляются в новых версиях.

Практика

  1. Реализуйте генератор Фибоначчи и take(n).
  2. Сделайте итерируемое бинарное дерево.
  3. Постройте for await для постраничной загрузки.

Вопросы с ответами

Связанные темы