Как избежать async/await hell

kak izbezhat asyncawait hell

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

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

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

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

Пример async/await hell

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

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

Объяснение

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

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

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

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

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

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

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

async function orderItems() {
  const items = await getCartItems()    // async call
  const noOfItems = items.length
  for(var i = 0; i < noOfItems; i++) {
    await sendRequest(items[i])    // async call
  }
}

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

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

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

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

(async () => {
  const value = doSomeAsyncTask()
  console.log(value) // an unresolved promise
})()

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

(async () => {
  const promise = doSomeAsyncTask()
  const value = await promise
  console.log(value) // the actual value
})()

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

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

Как выйти из async/await hell?

Вы должны выполнить эти шаги, чтобы избежать async/await hell.

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

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

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

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

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

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

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

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

Закрепим примеры

После трех шагов применим их на наших примерах.

async function selectPizza() {
  const pizzaData = await getPizzaData()    // async call
  const chosenPizza = choosePizza()    // sync call
  await addPizzaToCart(chosenPizza)    // async call
}

async function selectDrink() {
  const drinkData = await getDrinkData()    // async call
  const chosenDrink = chooseDrink()    // sync call
  await addDrinkToCart(chosenDrink)    // async call
}

(async () => {
  const pizzaPromise = selectPizza()
  const drinkPromise = selectDrink()
  await pizzaPromise
  await drinkPromise
  orderItems()    // async call
})()

// Although I prefer it this way 

Promise.all([selectPizza(), selectDrink()]).then(orderItems)   // async call

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

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

async function orderItems() {
  const items = await getCartItems()    // async call
  const noOfItems = items.length
  const promises = []
  for(var i = 0; i < noOfItems; i++) {
    const orderPromise = sendRequest(items[i])    // async call
    promises.push(orderPromise)    // sync call
  }
  await Promise.all(promises)    // async call
}

// Although I prefer it this way 

async function orderItems() {
  const items = await getCartItems()    // async call
  const promises = items.map((item) => sendRequest(item))
  await Promise.all(promises)    // async call
}

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

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *