Асинхронные генераторы
Асинхронный генератор — функция, объявленная как
async function*, которая может использоватьawaitвнутри и возвращает асинхронный итерируемый объект, позволяя перебирать данные по мере их поступления черезfor await...of.
Зачем нужно
Асинхронные генераторы решают задачу ленивого получения данных из асинхронных источников — потоков, пагинированных API, WebSocket. В отличие от Promise.all, они не ждут всех данных сразу и обрабатывают каждую «порцию» по мере готовности, экономя память.
Где используется
- Постраничный обход данных из API (pagination)
- Чтение файлов или потоков по частям
- WebSocket, Server-Sent Events — обработка сообщений по мере поступления
- Генерация ленивых последовательностей с асинхронными операциями
Синтаксис
async function* generatorName {
yield await someAsyncOperation;
yield await anotherAsyncOperation;
}
// Использование
for await (const value of generatorName) {
console.log(value);
}
Примеры
Пагинированный API
async function* fetchPages(baseUrl) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${baseUrl}?page=${page}&limit=10`);
const data = await response.json();
yield data.items; // отдаём массив элементов текущей страницы
hasMore = data.hasNextPage;
page++;
}
}
// Обработка
async function loadAllUsers() {
for await (const users of fetchPages('/api/users')) {
users.forEach(user => renderUser(user));
// данные обрабатываются по странице, не ждём всё сразу
}
}
Чтение потока по строкам
async function* readLines(stream) {
const reader = stream.getReader();
const decoder = new TextDecoder();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read;
if (done) {
if (buffer) yield buffer;
break;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop(); // последняя неполная строка остаётся в буфере
for (const line of lines) {
if (line) yield line;
}
}
} finally {
reader.releaseLock();
}
}
const response = await fetch('/api/large-file');
for await (const line of readLines(response.body)) {
console.log(line);
}
Комбинирование генераторов
async function* take(iterable, n) {
let count = 0;
for await (const item of iterable) {
yield item;
if (++count >= n) return;
}
}
// Взять только первые 3 страницы
for await (const page of take(fetchPages('/api/items'), 3)) {
process(page);
}
Частые ошибки
- Обычный
for...ofвместоfor await...of— обычныйfor...ofне понимает асинхронный итерируемый объект; нужен именноfor await...of. - Отсутствие обработки ошибок — исключения внутри генератора нужно ловить через
try...catchили передавать вgenerator.throw. - Не завершать генератор при ранней остановке — если цикл прерывается
break, вызывайтеgenerator.return()или используйтеtry/finallyдля освобождения ресурсов.
Связанные темы
Ресурсы
🎓 Источник: Генераторы и асинхронные генераторы в JavaScript
- 📅 2019-03-05 · YouTube
- Тезисы:
AsyncGeneratorFunction— отдельный скрытый класс,nextвозвращает Promise от{ value, done }.- Контракт
[Symbol.asyncIterator]— асинхронный аналог[Symbol.iterator]. awaitвстроен внутрьfor await...of— итерация ждёт каждую порцию.spread(...) НЕ работает с async-генератором — нет синхронного "собирателя". НуженPromise.all(arrayFromAsync)или вручную накапливать.- В синхронном генераторе можно
yield promise— но циклfor-ofне будет ждать. awaitвместоnext.thenделает код плоским при ручной работе с async-iterator.
🎓 Источник: Неблокирующее асинхронное итерирование в JavaScript
- 📅 2018-11-21 · YouTube
- Тезисы:
- Тяжёлый синхронный цикл блокирует event loop. Для неблокирующего обхода большого массива — разбивать на чанки и уступать через
setImmediate/queueMicrotask/await Promise.resolve. - Асинхронный генератор естественным образом отдаёт управление после каждого
yield— это идеальная абстракция для неблокирующего обхода.
- Тяжёлый синхронный цикл блокирует event loop. Для неблокирующего обхода большого массива — разбивать на чанки и уступать через
🎓 Источник: Итераторы и асинхронные итераторы в JavaScript
- 📅 2019-03-05 · YouTube
- Тезисы:
- Асинхронный iterable: метод
[Symbol.asyncIterator]→ объект сnextвозвращающим Promise. for await...ofотрабатываетdone: trueтак же, как обычныйfor-of.- Не существует встроенного
await spread— для сбора всех значений async-iterable нуженfor await + pushили специальные утилиты.
- Асинхронный iterable: метод