Блог

Цепочки промисов мертвы. Да здравствует Async/Await!

Редакция Lodoss Team
0

Не смотря на то, что async/await является современным подходом к работе с асинхронностью в JavaScript, очень многие программисты продолжают использовать устаревшие колбеки и цепочки промисов.

​​Почему?

Основным заблуждением является то, что async/await и промисы — это совершенно разные вещи.

​​Использование промисов не заставляет нас реализовывать нагромождение цепочек из этих самых промисов — это лишь технология, которую можно использовать в понятном и легко поддерживаемом коде.

В этой статье мы рассмотрим, как async/await действительно упрощает жизнь разработчиков и почему вы должны прекратить использовать цепочку промисов.

Давайте взглянем на цепочку промисов:

Теперь посмотрим на тот же код, написанный с помощью async/await:

Хмм, это похоже на простой синтаксический сахар, верно?

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

Рассмотрим почему.

Легко читать, легко поддерживать

Представьте, что нам нужно реализовать суперкрохотное изменение в нашем предыдущем коде (например, нам нужно упомянуть номер проблемы в тексте email письма — что-то вроде Some text #issue-number).

Как мы это сделаем? Для версии async/await все просто:

Первые две строки не трогаем, а третья требует минимального вмешательства.

А как насчет версии с цепочками промисов? Что ж, посмотрим.

В последнем .then() у нас есть доступ к owner, но нет к ссылке на issue. Вот где цепочка промисов начинает запутываться. Мы могли бы исправить код чем-то подобным:

Как видите, небольшая корректировка требует изменения нескольких строк ранее красивого кода (например, getOwner(issue.ownerId)).

Код постоянно меняется

Это особенно актуально при реализации новых задач. Предположим, нам нужно включить дополнительную информацию в текст e-mail письма, которое поступает от асинхронного вызова к функции getSettings().

Это может выглядеть так:

Как бы вы сделали это, используя цепочку промисов? Получится что-то вроде этого:

Код выглядит небрежным каждый раз, когда нужно поменять реквизиты и внести слишком много изменений в коде.

Для того, чтобы не громоздить вызов then() еще больше, можно запараллелить getIssue() и getSettings(), часто для этого используется Promise.all() с последующей деструктуризацией. Да, эта версия оптимальна по сравнению с версией await, потому что все работает параллельно, но ее все равно намного сложнее читать.

Можем ли мы оптимизировать версию await, чтобы все работало параллельно, не жертвуя удобочитаемостью кода?

Давайте удалим await в правой части назначения settings и переместим его в вызов sendEmail(). Таким образом, мы создаем промис, но не ждем его, пока нам не понадобится это значение. В то же время другой код может выполняться параллельно. Все просто!

Не используйте Promise.all() с неконтролируемым размером массива

Есть ли подводные камни при использовании Promise.all()?

Некоторые утверждают, что идеальный вариант его использования — это когда у вас есть массив значений и вам нужно использовать map() для формирования массива промисов, а далее получить массив с результатами. Например, у вас есть массив имен файлов, которые вам нужно прочесть, или массив URL-адресов, которые нужно загрузить. Однако, на большом массиве мы можем либо положить сторонний сервис огромным количеством параллельных запросов, либо положить свой сервер огромным количеством параллельных промисов.

Вместо этого лучше использовать внешнюю библиотеку для обработки параллельных запросов. Например, Promise.map() от bluebird, где можно настроить лимит параллельных запросов. Если нам нужно загрузить N файлов, с помощью этой утилиты мы можем указать, что одновременно будет загружено не более M файлов. Это позволит ограничить максимальное количество параллельных обработок «потоков» и тем самым снизить пиковые нагрузки.

Вы можете использовать await почти везде

Async/await превосходен, когда вы пытаетесь упростить код. Представьте, насколько сложнее эти выражения были бы с цепочкой промисов. С async/await вы получаете простой и чистый код.

Все еще не убедили?

Допустим, вы не разделяете нашей страсти к красивому коду и простоте обслуживания. Вместо этого вам нужны неопровержимые факты. Существуют ли они?

Да.

При включении цепочки промисов в свой код разработчики создают новые функции каждый раз при вызове then(). Процесс занимает больше памяти, а созданные функции всегда находятся в другом контексте. Таким образом, эти функции становятся замыканиями, и это затрудняет утилизацию памяти. Кроме того, они обычно являются анонимными функциями, которые загрязняют трассировки стека.

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

«Пока разработчик придерживается использования только асинхронных функций и асинхронных генераторов, ему не нужно писать код промисов вручную.»

Это не сработает, если вы используете цепочку промисов. Вот еще одна причина всегда использовать async/await!

Как перенести

Первое (и самое очевидное): начать использовать асинхронные функции и прекратить использовать цепочку промисов.

Второе: попробовать Visual Studio Code — она супер удобна для перехода на async/await.

Выводы

  1. Async/await уже широко поддерживается. Если вам не нужно обслуживать IE, вы в выигрыше.
  2. Async/await намного более читаем и прост в обслуживании.
  3. Есть весомые технические причины использовать только async/await.
  4. С помощью Visual Studio Code и, возможно, других IDE вы легко перейдете с существующего кода с цепочкой промисов на версию async/await!
0