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

В этой статье я попытаюсь объяснить, что такое async / await hell, а также поделюсь некоторыми советами, как избежать этого.

Что такое async / await hell

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

Пример async / await hell

Подумайте, написали ли вы сценарий для заказа пиццы и напитка. Скрипт может выглядеть так:

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

Объяснение

Мы обернули наш код в асинхронный IIFE. Следующее происходит именно в этом порядке:

  1. Получите список пицц.
  2. Получите список напитков.
  3. Выберите одну пиццу из списка.
  4. Выберите один напиток из списка.
  5. Добавьте выбранную пиццу в корзину.
  6. Добавьте выбранный напиток в корзину.
  7. Закажите товары в корзине.

Так что не так?

Как я подчеркивал ранее, все эти операторы выполняются один за другим. Здесь нет параллелизма. Подумайте внимательно: почему мы ждем, чтобы получить список пицц, прежде чем пытаться получить список напитков? Мы должны просто попытаться собрать оба списка вместе. Однако, когда нам нужно выбрать пиццу, нам нужно заранее составить список пиццы. То же самое и с напитками.

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

Еще один пример плохой реализации

Этот фрагмент кода JavaScript получит товары из корзины и разместит запрос на их заказ.

В этом случае цикл for должен дождаться завершения функции sendRequest(), прежде чем продолжить следующую итерацию. Однако на самом деле нам не нужно ждать. Мы хотим отправить все запросы как можно быстрее, а затем мы можем дождаться их завершения.

Я надеюсь, что теперь вы приближаетесь к пониманию того, что такое async / await hell и насколько сильно это влияет на производительность вашей программы. Теперь я хочу задать вам вопрос.

Что, если мы забудем ключевое слово await?

Если вы забыли использовать await при вызове асинхронной функции, функция начнет выполняться. Это означает, что ожидание не требуется для выполнения функции. Функция async вернет обещание, которое вы можете использовать позже.

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

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

Как видите, doSomeAsyncTask() возвращает обещание. На этом этапе doSomeAsyncTask() начал свое выполнение. Чтобы получить разрешенное значение обещания, мы используем ключевое слово await, которое укажет JavaScript не выполнять следующую строку немедленно, а вместо этого дождаться разрешения обещания и затем выполнить следующую строку.

Как выйти из асинхронного / ждущего ада?

Вы должны выполнить следующие действия, чтобы избежать async / await hell.

Найдите операторы, которые зависят от выполнения других операторов

В нашем первом примере мы выбирали пиццу и напиток. Мы пришли к выводу, что перед выбором пиццы нам необходимо иметь список пицц. И прежде чем добавить пиццу в корзину, нам нужно выбрать пиццу. Таким образом, мы можем сказать, что эти три шага зависят друг от друга. Мы не можем сделать что-то одно, пока не закончим предыдущее.

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

Таким образом, мы обнаружили некоторые операторы, которые зависят от выполнения других операторов, а некоторые - нет.

Группозависимые операторы в асинхронных функциях

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

Выполнять эти асинхронные функции одновременно

Затем мы используем цикл событий для одновременного запуска этих асинхронных неблокирующих функций. Два распространенных шаблона - ранний возврат обещаний и метод Promise.all.

Давайте исправим примеры

Выполнив три шага, давайте применим их к нашим примерам.

Теперь мы сгруппировали операторы в две функции. Внутри функции каждый оператор зависит от выполнения предыдущего. Затем мы одновременно выполняем обе функции selectPizza() и selectDrink().

Во втором примере нам нужно иметь дело с неизвестным количеством обещаний. Справиться с этой ситуацией очень просто: мы просто создаем массив и помещаем в него обещания. Затем, используя Promise.all(), мы одновременно ждем выполнения всех обещаний.

Я надеюсь, что эта статья помогла вам выйти за рамки основ async / await, а также помогла вам повысить производительность вашего приложения.

Если вам понравилась статья, пожалуйста, хлопните в ладоши. Совет - Вы можете хлопать 50 раз!

Пожалуйста, поделитесь также в Fb и Twitter. Если вы хотите получать обновления, подписывайтесь на меня в Twitter и Medium или подпишитесь на мою рассылку! Если что-то неясно или вы хотите что-то указать, прокомментируйте ниже.