В этом небольшом посте я хочу рассказать об одном интересном предложении (англ. proposal) в стандарт EcmaScript. Речь пойдёт об асинхронных итераторах, о том, что это такое, как ими пользоваться и зачем они вообще нужны простому разработчику. Асинхронные итераторы, это расширение возможностей обычных итераторов, которые с помощью цикла for-of/for-await-of позволяют пробежать по всем элементам коллекции. Для начала стоит объяснить, что я подразумеваю под генераторами, а что под итераторами, т.к. я часто буду использовать эти термины. Генератор — функция, которая возвращает итератор, а итератор — объект, содержащий метод next(), который в свою очередь возвращает следующее значение. Хотелось бы несколько подробнее остановиться на итераторах и объяснить их смысл в настоящее время. Современный JavaScript (стандарт ES6/ES7) позволяет перебрать значения коллекции (например Array, Set, Map и т.д.) поочерёдно, без лишней возни с индексами. Для этого был принят протокол итераторов, определяемый в прототипе коллекции с помощью символа (Symbol) Symbol.iterator: Каждый итератор (в нашем случае это range[Symbol.iterator]()) имеет метод next(), который возвращает объект, содержащий 2 поля: value и done, содержащие текущее значение и флаг, обозначающий конец генератора, соответственно. Этот объект можно описать таким интерфейсом: Более подробно о генераторах можно почитать на MDN. К слову, если у нас уже есть итератор и мы хотим пройтись по нему с помощью for-of, то нам не нужно приводить его обратно к нашему (или любому другому итерируемому) типу, т.к. каждый итератор имеет такой же метод , который возвращает this: Надеюсь, здесь всё понятно. Теперь ещё немного нужно сказать про асинхронные функции. В ES7 был предложен async/await синтаксис. По сути, это сахар позволяющий в псевдосинхронном стиле работать с промисами (Promise): Отличие от обычной функции в том, что async-функция всегда возвращает Promise, даже, если мы делаем обычный return 1, то получим Promise, который при разрешении вернёт 1. Отлично, теперь наконец-то переходим к асинхронным итераторам. Вслед за асинхронными фнкциями (async function () { ... }) были предложены асинхронные итераторы, которые можно использовать внутри этих самых функций: В данный момент асинхронные итераторы находятся в предложениях, в 3-й стадии (кандидат), что означает, что синтаксис стабилизирован и ожидает включения в стандарт. Это предложение пока не реализовано ни в одном JavaScript-движке, но попробовать и поиграть с ним всё же можно — с помощью Babel плагина babel-plugin-transform-async-generator-functions: взято из блога 2ality, полный код с примерами использования можно посмотреть в rauschma/async-iter-demo Итак, чем же асинхронные итераторы отличаются от обычных? Как говорилось выше, итератор возвращает значение IteratorResult. Асинхронный же итератор всегда возвращает Promise<IteratorResult>. Это значит, что для того, чтобы получить значение и понять нужно продолжать выполнение цикла или нет, нужно дождаться разрешения (resolve) промиса, который вернёт IteratorResult. Именно поэтому был введён новый синтаксис for-await-of, который и делает всю эту работу. Возникает закономерный вопрос: зачем было вводить новый синтаксис, почему нельзя вернуть IteratorResult<Promise>, а не Promise<IteratorResult> и подождать (await ...) его руками (прошу прощения за это странное выражение)? Это сделано для тех случаев, когда мы изнутри синхронного генератора не можем определить есть ли следующее значение или нет. Например нужно сходить в некую удалённую очередь по сети и забрать следующее значение, если очередь опустела, то выйти из цикла. Хорошо, с этим разобрались, остался последний вопрос — использование асинхронных генераторов и итераторов. Здесь всё достаточно просто: добавляем к генератору ключевое слово async и у нас получается асинхронный генератор: Если мы хотим чтобы наша собственная структура могла быть асинхронно проитерирована с помощью for-await-of, то нужно реализовать метод : На этом всё. Надеюсь эта статья была интересна и хоть в какой-то мере полезна. Спасибо за внимание. Нажиматель кнопок