Оригинальный адрес:How to escape async/await hell
Перевод с:Личный блог Night Town Song
async/await спасает нас от ада обратных вызовов, но при злоупотреблении мы попадаем в ад async/await.
В этой статье я объясню, что такое ад async/await, и поделюсь некоторыми советами, как его избежать.
Что такое ожидание/асинхронный ад
В асинхронном программировании Javascript мы обычно пишем много асинхронных методов и используемawait
ключевое слово, чтобы дождаться его, во многих случаях выполнение следующей строки не зависит от предыдущей строки, но мы все равно используемawait
ждать, поэтому могут возникнуть некоторые проблемы с производительностью.
Пример ожидания/асинхронного ада
Как написать код для заказа пиццы и напитков? Это может выглядеть так:
(async () => {
const pizzaData = await getPizzaData() // async call
const drinkData = await getDrinkData() // async call
const chosenPizza = choosePizza() // sync call
const chosenDrink = chooseDrink() // sync call
await addPizzaToCart(chosenPizza) // async call
await addDrinkToCart(chosenDrink) // async call
orderItems() // async call
})()
Он выглядит хорошо и работает нормально, но это не очень хорошая реализация. Давайте посмотрим, что делает этот код, чтобы найти проблему.
пожалуйста, объясни
Мы используем кодasync IIFE
Заверните его, а затем последовательно будет выполнено следующее.
- Получить меню пиццы
- Получить меню напитков
- Выбрать пиццу из меню пиццы
- Выберите напиток из меню напитков
- Добавить выбранную пиццу в корзину
- Добавьте выбранные напитки в корзину
- разместить заказ
Где не так?
Как я только что подчеркнул, эти заявления будутВыполнять последовательно без параллелизма. Если подумать, почему я должен получить меню пиццы, прежде чем я получу меню напитков? Я должен получить оба меню одновременно. Конечно, вы должны получить меню пиццы, прежде чем выбрать пиццу, и то же правило относится к напиткам.
Таким образом, мы можем сделать вывод, что работа, связанные с пиццей, связанной с пиццей, которые связаны с пиццей, но различные шаги, связанные с необходимыми, связанными с пиццей рабочей (шаг за шагом).
еще один плохой пример
Этот код получает товары в корзину и делает запрос на заказ.
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 и как сильно он влияет на производительность. Теперь я хочу задать вам вопрос.
Что, если вы забудете ключевое слово await?
Если вы забудете использовать await, асинхронная функция выполнится и вернет обещание, которое вы сможете разрешить позже.
(async () => {
const value = doSomeAsyncTask()
console.log(value) // an unresolved promise
})()
Другим последствием является то, что компилятор не знает, что вы хотите полностью выполнить функцию, поэтому компилятор выйдет из программы, не завершив асинхронную функцию, поэтому вам все равно нужно использовать ключевое слово await.
Интересной особенностью промисов является то, что вы можете получить промис в одной строке кода, а ожидание и разрешение — в другой, что является ключом к тому, чтобы избежать ада асинхронности/ожидания.
(async () => {
const promise = doSomeAsyncTask()
const value = await promise
console.log(value) // the actual value
})()
Как вы видете,doSomeAsyncTask()
Метод возвращает промис, который уже начал выполняться на момент его вызова.Чтобы получить его проанализированное значение, мы используем ключевое слово await, чтобы указать компилятору дождаться завершения синтаксического анализа перед выполнением следующей строки.
Как избежать асинхронного/ожидающего ада
Вы должны выполнить следующие шаги, чтобы избежать async/await hell:
Найдите зависимости оператора
В первом примере мы выбрали пиццу и напиток. Подводя итог, меню пиццы необходимо получить перед выбором пиццы, а меню пиццы необходимо выбрать перед добавлением в корзину.Эти три шага взаимозависимы и должны дождаться завершения предыдущего шага, прежде чем переходить к следующему шагу.
Наш выбор напитков не зависит от выбора пиццы, поэтому выбор пиццы и напитка может осуществляться параллельно. Это также одна из вещей, которые машины могут делать лучше нас.
Инкапсулируйте взаимозависимые асинхронные методы
Как видите, зависимости для выбора пиццы — получение меню пиццы, выбор, добавление в корзину. Поэтому мы помещаем эти зависимости в асинхронный метод, то же самое верно и для напитков, поэтому у нас естьselectPizza()
а такжеselectDrink()
Два асинхронных метода.
Параллельное исполнение
Мы используем неблокирующий цикл событий, который будет выполняться параллельно этим асинхронным методам, обычно будут использоваться два метода, чтобы вернуться как можно скорееPromise
и использовать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
(async () => {
Promise.all([selectPizza(), selectDrink()]).then(orderItems) // async call
})()
Мы инкапсулируем взаимозависимые операторы в их собственные функции и теперь выполняем их одновременно.selectPizza()
а такжеselectDrink()
Во втором примере нам нужно иметь дело с неизвестным количествомPromise
. Обработать эту ситуацию легко, мы сначала помещаем промисы в массив, а затем используем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 и поможет улучшить производительность вашей программы.