![](https://habrastorage.org/files/99f/a5d/26d/99fa5d26d8cd4b50a69e34f7d54f1bce.png)
Автор: Александр Трищенко, Senior Front-end Developer, DataArt
Содержание:
• Итераторы. Генераторы.
• Использование генераторов (Redux, Koa)
• Зачем нам использовать koa.js
• Будущее. Async Await и koa.js 2.x
Генераторы — новая спецификация, новая возможность, которую мы можем использовать в ECMAScript 6. Статью я начну с рассказа об итераторах, без которых понять генераторы не получится, расскажу непосредственно про спецификацию и о том, что такое генераторы вообще, про их использование в реальных кейсах. Рассмотрим два примера: React + Redux как фронтненд-случай и koa.js в качестве бэкенда. Затем подробнее остановлюсь на koa.js, будущем JavaScript, на асинхронных функциях и koa.js 2.
В статье использованы, в том числе, и заимствованные сниппеты (ссылки на источник приведены в конце), и я сразу прошу прощения, что части кода выложены в виде картинок.
![](https://habrastorage.org/files/9c3/ad0/b6c/9c3ad0b6c7404f59bbb99db40c6e04f3.png)
ECMAScript 6 (2015) поддерживается достаточно хорошо, чтобы его использовать. На диаграмме видно, что в принципе все неплохо даже у Microsoft в Edge, большие проблемы наблюдаются только у Internet Explorer (вертикальная ось координат — поддержка функциональности, в %). Приятно удивляет Safari 10, по заявлениям команды WebKit, работает все.
Итераторы
• Теперь все, что можно перебрать, – итерируемый объект (iterable).
• Все, что не перебирается само по себе, — можно заставить с помощью своего Symbol.iterator.
Каждый перебираемый, итерируемый тип данных, каждая итерируемая структура данных, получает итератор и становится iterable. Можно перебрать строку, массив, можно перебрать новые структуры данных, такие как Map и Set — все они должны содержать свой итератор. Теперь мы можем получить доступ непосредственно к самому итератору. Также появилась возможность заставить перебираться неперебираемое, и порой это может быть очень удобно.
![](https://habrastorage.org/files/fc6/a6a/d8e/fc6a6ad8e0d645048045672edc92733c.png)
Что такое итератор? Как вы можете видеть, существует простейший массив. У простейшего массива есть ключ — символ итератора, по сути, фабрика, которая возвращает итератор. Каждый вызов этой фабрики вернет новый экземпляр итератора, мы можем перебирать их независимо друг от друга. В итоге мы получили переменную, хранящую ссылку на итератор. Далее, с помощью единственного метода next, мы его перебираем. Метод next возвращает нам объект, который содержит два ключа, первый — value — непосредственно значение итератора, второй — состояние итератора — done: false.
![](https://habrastorage.org/files/8b0/b6c/2bf/8b0b6c2bfcd949babd79663de09a698f.png)
Описать итератор мы можем самостоятельно. В принципе, он представляет собой фабрику, обычную функцию. Предположим, что у нас есть функция endlessNumbers, есть индекс и метод next. Объект с единственным методом next, который возвращает итерируемое значение и статус. Этот итератор никогда не дойдет до конца, потому что мы никогда не будем присваивать ключу done значение true.
Применяются итераторы довольно широко, особенно в приложениях, которые реализуют нестандартные подходы к работе с информацией. В свободное время я занимаюсь написанием секвенсора на JavaScript с использованием Web Audio API. У меня есть задача: проигрывать с определенными интервалами какую-то ноту. Укладывать это в какой-то цикл было бы неудобно, поэтому я использую итератор, который просто «плюется» нотами в медиаплеер.
![](https://habrastorage.org/files/7cc/0d1/f39/7cc0d1f3995b4c81940dcd9aa4a69e5a.png)
Предпосылки для появления генераторов возникли уже давно. Вы можете увидеть динамику популярности Node.js за последние пять лет, она косвенно отражает популярность JavaScript в целом. Также на график отражает частоту запроса Callback Hell — он пропорционально зависит от распространения JavaScript. То есть, чем популярнее становился JavaScript, тем больше страдали разработчики и клиенты.
![](https://habrastorage.org/files/276/c4e/6ab/276c4e6abdc54f7a8528547bfa41e087.png)
Лапша, представленная на изображении – это структурная визуализация кода, написанного без генераторов. То есть это то, с чем всем нам приходится работать – мы к этому привыкаем и, не имея выбора, воспринимаем как данность. Когда разработчики начали попытки борьбы с этим явлением, появилась такая штука как promise. Идея была в том, чтобы взять все наши Callback (функции обратного вызова) и «размазать» их по всему коду, объявляя там, где нам удобнее. Однако на самом деле у нас остались те же самые функции обратного вызова, просто представленные в немного другом виде. Разработчики продолжили борьбу – так появились генераторы и асинхронные функции.
Генераторы
Применение
• Написание синхронного кода.
• Написание приостанавливаемых функций.
• Написание комплексных итераторов.
Генераторы позволяют писать синхронный код. Раньше я говорил, что достоинство JavaScript как раз в его асинхронности, теперь поговорим, как писать на JavaScript синхронный код. На самом деле, это код будет псевдосинхронным, поскольку Event Loop не будет останавливаться, если у вас есть какие-то тайм-ауты в фоне, пока вы будете ожидать выполнения приостановленного генератора. Вы вполне можете выполнять в фоновом режиме любые другие нужные операции. Мы можем написать приостанавливаемые функции, применение которых достаточно узко, в отличие от асинхронных действий, применяемых очень широко. Появилась и возможность написания комплексных итераторов. Например, мы можем написать итератор, который будет каждый раз ходить в базу и по очереди итерировать одно значение из нее.
![](https://habrastorage.org/files/c10/7d7/442/c107d7442d1d4fbc90e049b8dd146c0e.png)
Сверху и снизу вы видите абсолютно идентичные по функциональности сниппеты. У нас есть некая функция, которая должна сходить в базу данных и забрать user.payment_id. После чего она должна сходить на внешнюю API и забрать payment details user, актуальные на текущий день. У нас возникает явление The Pyramid Of Doom, когда функция обратного вызова находится внутри другой функции обратного вызова, и степень вложенности может увеличиваться бесконечно. Естественно, результат не всегда получается приемлемым, даже инкапсулировав все операции в отдельные функции, на выходе мы все равно получаем «лапшу».
Есть решение, которое позволяет нам сделать то же самое с помощью генератора: у генератора немного другой синтаксис — function* (со звездочкой). Генератор может быть именованный и неименованный. Как вы можете видеть, у нас появилось ключевое слово yield. Оператор yield, который приостанавливает выполнение генератора, позволяет нам дождаться выполнения метода GetUser. Когда мы получим данные, положим их в user, после чего продолжим выполнение, таким же образом получим paymentDetails, и тогда сможем отрисовать всю информацию для нашего пользователя.
![](https://habrastorage.org/files/03e/308/00a/03e30800a9004086ac3a47f1f23b619d.png)
Рассмотрим возможность реализации генераторов — как мы можем их перебирать. Здесь мы видим уже описанную ранее конструкцию. Как было показано на итераторе, здесь тоже есть итератор числа, который будет нам возвращать значение от 0 до 3, и который мы будем перебирать. То есть, мы можем использовать метод next.
Метод next()
• Может принимать в качестве аргумента значение, которое будет проброшено в генератор
• Возвращаемое значение – объект с двумя ключами:
value — часть выражения, получаемая из генератора.
done — состояние генератора.
Метод next ничем не отличается от аналогичного в итераторах, мы можем получить value и done, как два параметра, можем пробросить значение в генератор и получить значение из генератора.
![](https://habrastorage.org/files/76e/39d/e31/76e39de31c2f4197bfa53deeeac4759c.png)
Следующий вопрос — производительность. Насколько имеет смысл использовать то, о чем мы говорили? На момент доклада средств тестирования в моем распоряжении не было, поэтому я написал свое. В результате тысячи итераций удалось добиться среднего значения по различным технологиям. В Chrome обещания и генераторы не сильно отличаются друг от друга, причем в большую сторону отличаются то одни, то другие. Если учесть, что время, затраченное на выполнение одной итерации с помощью Callback, Promise или генераторов, исчисляется в миллисекундах, в реальности особенной разницы нет. Я думаю, что заморачиваться, экономя на спичках, не стоит. А значит, можно свободно использовать то, что вам больше по душе.
![](https://habrastorage.org/files/2d0/e65/584/2d0e65584d8d432c89b7c0568cf8038e.png)
Ни один современный доклад о JavaScript не может обойтись без React. Я в частности буду говорить о Redux.
redux-saga
• Это библиотека.
• Это библиотека, написанная на генераторах.
• Это библиотека, которая прячет impure-функции с глаз долой.
• Это библиотека, которая позволяет вам писать синхронный код.
В функциональном программировании есть очень важный принцип — мы должны использовать «настоящие функции». Наша функция не должна влиять на окружение, должна работать с теми аргументами, которые мы ей передаем. В том или ином виде наши функции обратного вызова часто превращаются в impure function, и этого, конечно, хочется избежать. Отсюда и основное назначение redux-saga — возможность писать синхронный (псевдосинхронный) код.
![](https://habrastorage.org/files/45a/19b/662/45a19b66296443b38651e8f5470ad09e.png)
Суть заключается в том, что таким же образом мы можем с помощью генераторов останавливать выполнение нашей саги. Можно сказать, что saga — своеобразный аналог action, который вызывает другой action. Дождавшись ответа, мы с помощью диспетчера инициируем нужное событие в нашем reducer и передаем необходимую информацию.
![](https://habrastorage.org/files/af9/474/825/af9474825a614961a57ae57afa3ecb97.png)
Суть достаточно проста: в результате мы выполнили асинхронное действие очень просто и быстро. Собственно, минимальная saga выглядит так: есть генератор, который обращается к нашей saga, вызывает takeEvery — один из методов saga, который позволяет нам инициировать событие “USER_FETCH_REQUESTED” внутри нашего редюсера. Возможно, вы обратили внимание, что yield здесь идет со звездочкой. Это является делегацией операции генератора, мы можем делегировать наш генератор другому генератору.
redux-saga: послесловие
• Саги (Sagas) не декларируются, как обычные Actions, их необходимо внедрять через sagaMiddleware.
• Очевидно, что сам sagaMiddleware — нечто иное, как middleware вашего store в Redux.
Мы поговорили про фронтенд, теперь пришло время рассказать про бэкенд, т. е. о koa. Я сталкивался со многими фреймворками на бэкенде, наиболее интересными для меня показались kraken.js и koa.js, на втором остановлюсь подробнее.
koa.js
В двух словах это:
• node.js-фреймворк для серверной разработки.
• node.js-фреймворк, который использует ES6-генераторы, асинхронные функции ES2016.
• node.js-фреймворк, написанный командой express.js.
Учитывая авторитетность команды express.js, ресурсы компании, фреймворк вызывает доверие и быстро развивается. На данный момент вокруг него образовалось солидное сообщество, он оброс кучей библиотек — зачастую найти какое-то решение для middleware koa очень просто.
• «Фреймворк нового поколения»
![](https://habrastorage.org/files/a6e/515/c34/a6e515c342ec4f1eb0b695f5f58e14f6.png)
Что такое koa? По сути, это фреймворк, который предоставляет нам движок для посредников (middleware), а их архитектурная диаграмма очень похожа на хорошо знакомую всем игру в испорченный телефон. Здесь имеется состояние, которое по очереди передается между middleware, каждый из которых влияет или не влияет на это состояние (я дальше покажу пример логера, который влияние не оказывает). С этим middleware мы и будем работать. Напомним, что koa.js — фреймворк middleware на генераторах. Т. е., если мы говорим о маршрутизации, о различных полезных HTTP-методах, о системах безопасности, защите от CSRF-атак, о кроссдоменных запросах, шаблонизаторах и т. д. — в koa ничего этого мы не найдем. В koa есть только движок для middleware, причем множество из них написано самой командой koa.js.
![](https://habrastorage.org/files/dd2/df6/e62/dd2df6e62d2e4431a5327723bab1b0f6.png)
Так выглядит максимально простое приложение на koa.js. Есть реализация логирования – простейшая имплементация middleware на koa.js. Это генератор, который возвращает свое состояние и перед тем, как его вернуть, и после того, как оно вернется, что позволяет подсчитать время, затраченное на выполнение нашего приложения. Обратите внимание, что выполняются они в порядке объявления: то, что объявили выше, начнет работать прежде всего.
koa.js
Преимущества:
• Наличие огромного количества библиотек, обернутых в co.js.
• Модульность и легковесность.
• Возможность писать более понятный код.
• Возможность писать меньше кода.
• Высокая активность сообщества.
Казалось бы, koa.js — бедный фреймворк, в котором нет почти ничего. В то же время, существует множество библиотек, и большая часть стандартного сервисного функционала представлена в виде middleware. Нужны кроссдоменные запросы — просто подключаете пакет и пробрасываете middleware. Если требуются настройки — необходимо просто передать параметры, и у вас будут кроссдоменные запросы. Необходима авторизация с помощью jwt- token — то же самое: понадобятся три строчки кода. Если необходимо работать с базой данных — пожалуйста.
Таких случаев много — работа с фреймворком становится похожа на игру с конструктором: от вас требуется только пробовать разные пакеты, и все будет работать. Таких возможностей инкапсуляции все очень ждали, и теперь они в нашем распоряжении. Как результат отсутствия функциональности внутри фреймворка, он стал легче, также отсутствуют какие-то стандартные компоненты, которые потом надо допиливать. Появилась возможность писать более понятный код. Генераторы позволяют писать псевдосинхронный код, таким образом, можно сократить количество непонятных и ненужных вещей в приложении. Вследствие этого появилась и возможность писать меньше кода. Присутствует активная поддержка сообщества, множество плагинов, которые начинают конкурировать между собой. Выигрывают лучшие, многие при этом отсеиваются, что в целом, конечно, полезно.
![](https://habrastorage.org/files/ac7/75b/ab2/ac775bab265e441db6da0ffaaaa8710e.png)
В таблице представлено сравнение поставки Koa, Express и Connect фреймворков. Как вы можете видеть, в koa нет ничего, кроме middleware ядра.
Стоит сказать пару слов о самом co.js:
Co.js — это обертка вокруг генераторов и обещаний, которая позволяет нам упростить работу с асинхронными операциями. Более корректно обозначить co как «Сопрограммы (coroutines) в JavaScript». Идея сопрограмм не нова, и существует в других языках программирования очень давно.
Основная идея заключается в передаче управления из основной программы в сопрограмму, которая в свою очередь может вернуть управление основной программе. Собственно часть этого процесса и реализуют генераторы в JavaScript.
Если привести все к более привычным для JS-разработчика материям — co.js выполняет генератор, избавляя нас от необходимости последовательного вызова next() генератора. В свою очередь co возвращает другое обещание, что позволяет нам отследить его завершение и отловить ошибки (для этого можно использовать метод catch). Самое крутое — в co можно выполнить yield для массива промисов (а ля Promise.all). Стоит заметить, что co прекрасно справляется с делегацией генераторов.
koa.js
Пара полезных пакетов для старта:
• koa-cors — разрешаем кроссдоменные запросы одной строкой.
• koa-route — полноценный роутинг.
• koa-jwt — cерверная реализация авторизации с использованием jwt-токена.
• koa-bodyparse — парсер тела приходящих запросов.
• koa-send — управление статикой.
Выше в качестве примера приведены несколько middleware, которые вы можете использовать в реальном приложении. koa-cors пакет позволяет обеспечить кроссдоменные запросы, koa-route обеспечивает роутинг, аналогичный тому, что есть в Express, и т. д.
![](https://habrastorage.org/files/2e4/470/82f/2e447082f15941028cf6b9bfc888b475.png)
Основные недостатки koa.js — обратная стороной того, что фреймворк поставляется голым, необходимость постоянно контролировать качество пакетов, которые при этом не обещают быть зависимыми друг от друга, и иногда избавляться от багов становится сложно. Вторая проблема — подбор команды, потому что на данный момент, к сожалению, с koa.js работает не так много людей. Из-за этого увеличивается время на введение нового человека в проект. И если проект маленький, это может оказаться нерентабельным. Т. ч. использовать koa.js в работе нужно с умом.
koa.js 2
Фреймворк нового поколения?
Koa.js 2 — очень хитрый фреймворк. Он работает на спецификации, которой нет. То есть вы можете найти статьи об асинхронных функциях, где сказано, что это ECMAScript 7 или ECMAScript 2016. Но самом деле несмотря на то, что Babel, Google Chrome и Microsoft Edge поддерживают асинхронные функции, их не существует. Многие ожидали, что асинхронные функции войдут в официальный релиз ECMAScript 7 (2016), но в итоге тот вышел с исправлениями дефектов и двумя новыми возможностями, чем новшества и ограничились. А тем временем koa.js 2 на асинхронных функциях работает, разработчики на них пишутся. И все это позиционируется как фреймворк нового поколения.
Async functions
Обзор
• Async — это Promise.
• Await — это Promise.
Асинхронные функции — и Async, и Await — это Promise.
![](https://habrastorage.org/files/19d/da4/944/19dda494450e4ce7b45462712d4514e6.png)
Допустим, у нас есть такой код. Если убрать async и await, поставить возле функции звездочку и поставить yield перед conquer, получится генератор. Казалось бы, в чем разница? А она в том, что мы ожидаем в conquer обычный Promise, не надо оборачивать наши асинхронные функции ни в какие генераторы, это просто не требуется — мы можем взять обычный новый метод для получения запроса сервера fetch. Потом необходимо дождаться результата, а когда мы его получим, положим в state и таким образом вернем состояние.
Async functions
Послесловие
• Асинхронные функции удобнее генераторов (меньше кода, нет необходимости оборачивать промисы для генераторов).
• Асинхронные функции пока еще не часть стандарта и не ясно, станут ли они его частью.
Асинхронные функции, безусловно, удобнее генераторов, они позволяют нам писать меньше кода. В этом случае нет необходимости писать обвязочный код, можно взять любую библиотеку, которая возвращает нам promise (а это почти все современные библиотеки). Это позволяет сэкономить много времени и денег. Минус — асинхронная функция — все еще черновик спецификации. Значит, в итоге может получиться так же, как с захватом экрана в WebRTC: появились приложения использующие эту функциональность, а в результате от нее отказались.
![](https://habrastorage.org/files/962/2fc/be9/9622fcbe90854588840b514c10a5723f.png)
Мораль всего, о чем я рассказывал в статье, довольно проста: я не говорю, что генераторы —замена Promise или Callback. Я не утверждаю, что асинхронные функции могут заменить генераторы, функции обратного вызова и обещания. Но у нас появились новые инструменты для написания кода, позволяющие делать его красивым и структурированным. Но их использование остается на вашем усмотрение. Решать стоит с точки зрения рациональности и применимости каждого инструмента в конкретно вашем проекте.
Список используемых ресурсов
Рисунки заимствовал тут: 1, 2, 3.
Сниппеты заимствовал тут.