W3docs

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

Асинхронные итераторы и генераторы в JavaScript: Symbol.asyncIterator, async function*, for await...of и пример ленивой постраничной загрузки.

Асинхронное программирование — краеугольный камень современной разработки на JavaScript. Оно позволяет писать неблокирующий, конкурентный код, эффективно обрабатывающий сетевые запросы, файловый ввод-вывод и таймеры. Это руководство посвящено асинхронным итераторам и асинхронным генераторам — двум возможностям, появившимся в ECMAScript 2018, которые позволяют перебирать данные, поступающие по мере готовности: один фрагмент за сетевой round-trip, одно событие на действие пользователя — без блокировки остальной программы.

Предполагается, что вы уже знакомы с обычными итерируемыми объектами, генераторами и промисами. Если что-то из этого вам незнакомо, прочитайте сначала соответствующие разделы.

Понимание асинхронных итераторов

Что такое асинхронные итераторы?

Асинхронные итераторы — особый вид итераторов, предназначенный для работы с асинхронными потоками данных. В отличие от традиционных синхронных итераторов, асинхронные позволяют перебирать последовательности асинхронных значений — промисов или потоков — без блокировки выполнения.

С технической точки зрения, объект считается асинхронно итерируемым, если он реализует метод Symbol.asyncIterator, возвращающий объект-итератор. Вот практический пример ручной реализации этого интерфейса на пользовательском объекте:

javascript— editable

Синхронные и асинхронные итераторы

Разница между обычным и асинхронным итерируемым объектом сводится к трём вещам: имени метода, типу значения, возвращаемого next(), и циклу для перебора.

Синхронный итерируемыйАсинхронный итерируемый
МетодSymbol.iteratorSymbol.asyncIterator
next() возвращает{ value, done }Promise от { value, done }
Цикл потребленияfor...offor await...of
Синтаксис генератораfunction*async function*

Поскольку в асинхронном случае next() возвращает Promise, каждый шаг цикла может ожидать завершения асинхронной операции — fetch-запроса, таймера, чтения из базы данных — прежде чем будет получено следующее значение. Обычный for...of на это не способен: он ожидает немедленной доступности value и done. Использование for await...of на синхронно итерируемом объекте по-прежнему работает (движок оборачивает значения в выполненные промисы), однако применение синхронного for...of к асинхронному итерируемому объекту не работает — вы просто получите перебор объектов pending Promise.

Как использовать асинхронные итераторы

Чтобы применять асинхронные итераторы в своём JavaScript-коде, нужно прежде всего разобраться с их основными концепциями и синтаксисом. Рассмотрим простой пример, демонстрирующий принцип работы асинхронных итераторов на практике:

javascript— editable

В этом примере мы определяем асинхронную функцию-генератор generateNumbers(), которая последовательно выдаёт числа асинхронно. Затем из функции-генератора создаётся асинхронный итерируемый объект, и цикл for await...of перебирает значения, производимые асинхронным итератором.

Примечание: Когда вы yield обычное значение внутри async function*, метод next() итератора автоматически возвращает Promise, который разрешается в { value: <ваше значение>, done: false }. Явно yield-ить Promise нужно только в том случае, если вы хотите, чтобы потребитель получил объект Promise, а не разрешённое значение.

Практическое применение асинхронных итераторов

Асинхронные итераторы широко применяются в сценариях, связанных с асинхронной обработкой данных: загрузка данных из внешних API, чтение из потоков, обработка асинхронных событий. Их универсальность и эффективность делают их незаменимым инструментом для современных JavaScript-разработчиков, создающих масштабируемые и отзывчивые приложения.

Знакомство с генераторами JavaScript

Введение в генераторы

Генераторы — мощная возможность, появившаяся в ECMAScript 2015, позволяющая создавать итерируемые последовательности с произвольной логикой итерации. В отличие от обычных функций, которые выполняются до конца при вызове, генераторы могут приостанавливать и возобновлять своё выполнение, обеспечивая ленивое вычисление значений.

Важно различать стандартные и асинхронные генераторы:

  • Стандартные генераторы (function*): возвращают значения синхронно.
  • Асинхронные генераторы (async function*): возвращают Promise при каждом вызове next(), позволяя потребителю ожидать каждое значение с помощью for await...of.

Применение генераторов для асинхронного программирования

Один из наиболее привлекательных вариантов использования генераторов — асинхронное программирование. Сочетая генераторы с промисами, разработчики могут создавать асинхронные рабочие процессы, которые одновременно элегантны и легко воспринимаются. Вот современный пример использования асинхронного генератора для получения и возврата данных с удалённого сервера:

javascript— editable

В этом примере мы определяем асинхронную функцию-генератор fetchTodos(), которая асинхронно загружает данные из удалённого API с помощью функции fetch(). Используя await внутри генератора и возвращая отдельные элементы через yield, мы можем передавать результаты прямо в цикл for await...of без ручных вызовов .next() и цепочек промисов.

Постраничная загрузка с асинхронным генератором

Шаблон, демонстрирующий все преимущества асинхронных генераторов, — ленивая пагинация. Многие API возвращают результаты постранично и требуют запрашивать следующую страницу до тех пор, пока данные не закончатся. Асинхронный генератор позволяет скрыть всю эту логику: он запрашивает страницу, поочерёдно возвращает её элементы и запрашивает следующую страницу только тогда, когда потребитель запрашивает ещё данные. Вызывающий код может прервать итерацию досрочно — например, найдя нужное — и никаких дополнительных сетевых запросов сделано не будет.

javascript— editable

Обратите внимание на break: поскольку генератор ленивый, выход из цикла после 25 элементов означает, что генератор никогда не запросит третью страницу. Именно это отличает асинхронный генератор от предварительной загрузки всего массива данных — вы платите только за те данные, которые реально используете.

Продвинутые шаблоны генераторов

Генераторы предлагают множество продвинутых шаблонов и техник для решения сложных задач программирования. Вот несколько примеров, демонстрирующих их универсальность:

  • Параллельное выполнение: запуская несколько генераторов и управляя их промисами конкурентно, можно выполнять несколько асинхронных задач одновременно.
  • Обработка ошибок: используйте блоки try-catch внутри генераторов для корректной обработки отклонённых промисов, возникающих в процессе итерации.
  • Конвейеры данных: создавайте конвейеры обработки данных, объединяя генераторы в цепочку, где результат одного генератора служит входными данными для следующего.
javascript— editable

Заключение

Асинхронные итераторы и генераторы — незаменимые инструменты в арсенале современного JavaScript-разработчика. Освоив эти мощные возможности, вы откроете новые измерения выразительности и эффективности в асинхронном коде. Разрабатываете ли вы веб-приложения, серверные API или утилиты командной строки — асинхронные итераторы и генераторы помогут справиться со сложными асинхронными задачами с лёгкостью. Начните применять их в своих JavaScript-проектах уже сегодня и выведите свои навыки программирования на новый уровень!

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

  • Итерируемые объекты — синхронная основа за for...of и Symbol.iterator.
  • Генераторы — синтаксис function*, на котором строятся асинхронные генераторы.
  • Промисы — то, что разрешает каждый await внутри асинхронного генератора.
  • Async/await — синтаксис, с которым используется for await...of.

Практика

Практика
Что верно об асинхронных итераторах и генераторах в JavaScript?
Что верно об асинхронных итераторах и генераторах в JavaScript?
Was this page helpful?