Итераторы и генераторы
Протокол итератора, Symbol.iterator, function* и ленивые бесконечные последовательности.
Итератор — объект с методом
next(), возвращающим{ value, done }. Итерируемый объект имеет метод[Symbol.iterator].
Протокол итерации вручную
Конструкции for...of и spread (...) работают с любым объектом, у которого есть [Symbol.iterator]. Реализуем «диапазон» с нуля:
const range = {
from: 1, to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) return { value: current++, done: false };
return { value: undefined, done: true };
}
};
}
};
console.log([...range].join(", "));
console.log("сумма:", [...range].reduce((a, b) => a + b, 0));Вывод:
1, 2, 3, 4, 5 сумма: 15
function* — генераторы упрощают всё
Генератор автоматически реализует протокол итератора. Каждый yield «ставит на паузу» функцию и отдаёт значение; next() возобновляет её:
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGenerator();
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);Вывод:
1 2 3
Ленивость: бесконечная последовательность
Генератор вычисляет значения по требованию. Бесконечный while (true) безопасен, потому что значения берутся ровно по запросу. Возьмём первые 5 натуральных чисел из бесконечного потока:
function* naturals() {
let n = 1;
while (true) yield n++;
}
function take(iterable, count) {
const result = [];
for (const x of iterable) {
if (result.length >= count) break;
result.push(x);
}
return result;
}
console.log(take(naturals(), 5).join(", "));Вывод:
1, 2, 3, 4, 5
Двусторонний обмен и yield*
Генераторы умеют не только отдавать, но и принимать значения через аргумент next(value). А yield* делегирует другому генератору:
function* dialog() {
const name = yield "Как тебя зовут?";
const age = yield "Привет, " + name + "! Сколько тебе лет?";
return name + ", возраст " + age;
}
const g = dialog();
console.log(g.next().value);
console.log(g.next("Аня").value);
console.log(g.next(25).value);Вывод:
Как тебя зовут? Привет, Аня! Сколько тебе лет? Аня, возраст 25
Значение, переданное в next("Аня"), стало результатом первого yield. Так строится двусторонний диалог с генератором.
function* inner() { yield "a"; yield "b"; }
function* outer() {
yield 1;
yield* inner(); // делегируем все yield из inner
yield 2;
}
console.log([...outer()].join(", "));Вывод:
1, a, b, 2
Итог
- Итерируемость даёт метод
[Symbol.iterator]; его используетfor...ofи spread. function*автоматически реализует протокол;yieldставит на паузу.- Генераторы ленивы — годятся для бесконечных потоков;
yield*делегирует.