Bluebird: магия внутри

Асинхронность. Асинхронность никогда не меняется. Node.js использовал асинхронность, чтобы получить большой rps для io-операций. TC39 добавила промисы в спецификацию для борьбы с адом колбеков. Наконец, мы стандартизировали async/await. Но асинхронность никогда не меняется. Погодите, что это синеет в небе? Похоже bluebird несёт в клюве пояс с инструментами для тех из нас, кто плотно подсел на thenable-объекты и всю эту асинхронную лапшу.


6cb9b0hhjixkkaqkfdbs9dusuy8.jpeg


Если кто незнаком, bluebird это библиотека, реализующая функционал промисов для javascript. Если в клиентскую сборку вы её вряд ли потащите, как никак 21Kb gzipped, то не использовать её на стороне сервера вы просто не имеете морального права. Bluebird всё ещё работает быстрее нативной реализации. Можете не верить на слово, а скачать репозиторий и запустить бенчмарки на последней версии Node.js (9.x.x). Подробней о преимуществах можно прочитать в кратком обзоре архитектурных принципов библиотеки.


Помимо счастья, библиотека дарит множество методов прямиком из завтрашнего дня, дополняющих базовый механизм промисов. Поэтому предлагаю ознакомиться с самыми интересными из методов. Надеюсь, это сподвигнет вас нырнуть поглубже в документацию, так как там ещё много всего вкусного.


Начнем с достаточно легкого и известного, следящими за новыми фичами в ECMAScript, а именно —  finally. Точно такой же метод теперь является частью спецификации (вошел в релиз ES2018). Позволяет зарегистрировать обработчик, срабатывающий в независимости от итогового состояния промиса (fullfiled, rejected).



Этот метод, как и старые добрые then и catch, возвращает новый промис, на который можно подписаться. Важно, что в случае перехода в состояние rejected, обработчик в finally не считается успешной обработкой ошибки, поэтому она продолжит распространение до первого обработчика catch.



И, конечно же, из обработчика finally можно вернуть промис. Оставшаяся цепочка дождётся его завершения вызовом последующих обработчиков.



Двигаемся дальше. Ребята прокачали метод catch —  с ним можно легко фильтровать возникающие ошибки, которые мы хотим обрабатывать:



Это стимулирует к написанию обработки ошибок в более атомарном стиле с хорошим потенциалом к переиспользованию кода. Также, помимо прототипа, можно использовать функцию предикат:



Один из замечательнейших методов библиотеки и крайне странно, что его нет в стандарте —  any.



Позволяет дождаться выполнения хотя бы одного промиса из переданного массива. Если более подробно, то промис, созданный методом any, перейдёт в состояние fullfiled, когда любой из промисов перейдет в это состояние. Обработчик в then получит значение из этого разрешенного промиса:



В случае, если все переданные промисы завершаться с ошибкой, то агрегирующий промис тоже перейдет в состояние rejected. Обработчик в catch получит специальную ошибку, объединяющую причины отказа всех промисов. Отметим, что порядок ошибок зависит от порядка их возникновения, а не от исходного порядка промисов.



По сути, метод any является специальной версией метода some с параметром count равным 1. Таким образом, через some мы можем явно задать условия для перехода агрегирующего промиса в состояние fulfilled:



Если у вас часто возникает необходимость параллельно запустить асинхронную операцию для каждого элемента массива и потом дождаться всех результатов, то вам знаком этот код:



Синяя птица предоставляет нам шорткат для этого:



Единственное, на что следует обратить внимание : для функции, передаваемой в качестве маппера, третьим параметром вместо массива нам придёт его длина. Также у метода map есть объект настроек, передаваемых после маппера. На данный момент опция только одна —  concurrency — контроллирующая, сколько промисов могут быть запущены параллельно:



А что будет, если задать concurrency равным 1? Верно, промисы будут выполняться последовательно. Для этого тоже есть шорткат:



Часто возникает ситуация, когда нужно передать какие-то промежуточные данные между обработчиками промиса в рамках цепочки. Можно использовать Promise.all и деструктуризацию для этих целей. Другим вариантом будет использование общего контекста, привязанного к обработчикам в then и catch с помощью метода bind:



Для случаев, когда функция, возвращающая промис, может сгенерировать синхронное исключение, вместо самостоятельного оборачивания кода в блок try/catch с последующим отклонением, можно воспользоваться утилитой try:



Ещё больше расширяет наши возможности утилита method. Дополнительно к отлову синхронных ошибок позволяет разрешать синхронные возвраты в автоматическом режиме, не задумываясь об обёртках. Например, будет полезным при мемоизации асинхронных операций. В отличии от try, method возвращает функцию высшего порядка:



Метод tap пригодится, если нужно в существующую цепочку вставить сайд-эффекты, не изменяющие данные, например, для логгирования:



Если сайд-эффект является асинхронной операцией, и важно дождаться её выполнения, привычно возвращаем промис из обработчика:



В наличии также версия метода для ошибок:



Также, как и с catch, можно сделать фильтрацию:



Следующая фича находится на рассмотрении у TC39 в рамках более обширной темы —  отмены асинхронных операций. Пока её ещё не завезли, мы можем довольствоваться малым и научиться отменять промисы:



Некоторые асинхронные операции можно отменять. Bluebird при создании промиса предоставит вам специальный метод для регистрации колбека, вызываемого при отмене:



Полезным бывает задать временные ограничения для операции. Тогда в нашем распоряжении метод timeout, который отклонит промис с ошибкой TimeoutError в случае истечения времени:



И, напоследок, для ментальной разгрузки. Если в силу непреодолимых обстоятельств необходимо отложить запуск асинхронной операции, то в этом поможет метод delay:



На этом нам следует распрощаться. Попробуйте синюю птицу в своих pet-проектах, а затем берите с собой в production. Увидимся на JS просторах!

© Habrahabr.ru