Асинхронные генераторы

Асинхронный генератор — функция, объявленная как 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 — это идеальная абстракция для неблокирующего обхода.

🎓 Источник: Итераторы и асинхронные итераторы в JavaScript

  • 📅 2019-03-05 · YouTube
  • Тезисы:
    • Асинхронный iterable: метод [Symbol.asyncIterator] → объект с next возвращающим Promise.
    • for await...of отрабатывает done: true так же, как обычный for-of.
    • Не существует встроенного await spread — для сбора всех значений async-iterable нужен for await + push или специальные утилиты.