Асинхронные итераторы и генераторы
for await, async function* и асинхронные итераторы — как обрабатывать потоки данных, приходящие частями.
Асинхронный итератор — объект, чей метод
next()возвращает промис; перебирается цикломfor await...of.
Зачем нужны
Обычный итератор отдаёт значения синхронно. Но данные часто приходят порциями во времени: страницы API, строки из сети, события. Асинхронные итераторы позволяют писать for await (const item of source) и получать элементы по мере готовности, не собирая всё в память сразу.
async function* — асинхронный генератор
Генератор с async может использовать await внутри и yield для выдачи значений. Каждое значение «созревает» асинхронно:
async function* range(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(r => setTimeout(r, 5)); // имитация асинхронного источника
yield i;
}
}
async function main() {
for await (const n of range(1, 4)) {
console.log("получено:", n);
}
console.log("поток завершён");
}
main();Вывод:
получено: 1 получено: 2 получено: 3 получено: 4 поток завершён
Постраничная загрузка — типичный сценарий
Представьте API, отдающий данные страницами. Асинхронный генератор инкапсулирует логику «запроси страницу, отдай элементы, перейди к следующей», а потребитель просто перебирает результат:
// имитация API: возвращает страницу из 2 элементов, пока не дойдём до конца
async function fetchPage(page) {
await new Promise(r => setTimeout(r, 3));
const data = { 1: ["a", "b"], 2: ["c", "d"], 3: [] };
return data[page] || [];
}
async function* paginate() {
let page = 1;
while (true) {
const items = await fetchPage(page);
if (items.length === 0) break;
for (const item of items) yield item;
page++;
}
}
async function main() {
const collected = [];
for await (const item of paginate()) {
collected.push(item);
}
console.log("все элементы:", collected.join(", "));
}
main();Вывод:
все элементы: a, b, c, d
Symbol.asyncIterator вручную
Под капотом for await ищет метод [Symbol.asyncIterator]. Свой асинхронно-итерируемый объект можно собрать и без генератора:
const asyncCountdown = {
from: 3,
[Symbol.asyncIterator]() {
let current = this.from;
return {
async next() {
await new Promise(r => setTimeout(r, 5));
if (current > 0) return { value: current--, done: false };
return { value: undefined, done: true };
}
};
}
};
async function main() {
for await (const n of asyncCountdown) {
console.log("тик:", n);
}
console.log("пуск!");
}
main();Вывод:
тик: 3 тик: 2 тик: 1 пуск!
Итог
async function*комбинируетawaitиyieldдля потоков данных во времени.for await...ofперебирает асинхронный источник по мере готовности элементов.- Под капотом — метод
[Symbol.asyncIterator], чейnext()возвращает промис.