Когда не использовать async/await или избегать связанных асинхронных задач
Во-первых, во многих ситуациях ключевые слова async
/ await
позволяют нам реализовать асинхронное поведение на основе промисов в очень чистом стиле, избегая необходимости явно настраивать цепочки промисов, что иногда может привести к путанице. Но иногда есть лучшая альтернатива async/await.
Введение
Рассмотрим следующий пример:
const user = await getUserById(id); const products = await getProducts(); return {user, products};
Здесь мы извлекаем конкретные данные о пользователе, а затем извлекаем список продуктов. Вы можете сказать: что в этом плохого? дело в том, что мы ждем, чтобы получить информацию о пользователе, прежде чем получить список продуктов, хотя получение списка продуктов не зависит от списка пользователей. . Так зачем ждать, если мы можем запускать их параллельно? Кроме того, это снижает производительность.
Теперь рассмотрим этот пример:
(async () => { const user = await getUserById(id); const products = await getProducts(); return {user, products}; })()
Мы обернули его с помощью IIFE, но все же этот код будет запускать функции getUserById()
и getProducts()
одну за другой, а не одновременно. Это связано с тем, что выражения await
в функции приостанавливают выполнение до тех пор, пока обещания, возвращаемые функциями getUserById()
и getProducts()
, не разрешатся.
Выражение await
можно использовать только внутри функции async
, и оно заставляет функцию приостанавливаться до тех пор, пока обещание, переданное ей в качестве аргумента, не будет разрешено. Это позволяет писать асинхронный код, который выглядит и ведет себя как синхронный код, что упрощает его чтение и понимание. Однако, если вы хотите выполнять промисы одновременно, вы можете использовать Promise.all или Promise.allSettled, что позволяет дождаться разрешения или отклонения нескольких промисов в в то же время в чистом виде.
Например, вы можете переписать код следующим образом для одновременного выполнения функций getUserById()
и getProducts()
:
(async () => { const [user, products] = await Promise.all([getUserById(id), getProducts()]); return {user, products}; })()
Это приведет к одновременному выполнению функций getUserById()
и getProducts()
, и функция приостановится на выражении await
, пока оба промиса не будут разрешены. Разрешенные значения будут деструктурированы в переменные user
и products
, и функция вернет объект, содержащий эти переменные, в качестве свойств.
Просто, элегантно 😄 и вдвое быстрее, потому что Promise.all выполняет их все одновременно.
Вы можете узнать больше о Promise.all или Promise.allSettled на MDN
Примечание. Стоит отметить, что Promise.all вернется либо в случае успешного выполнения всех обещаний, либо в случае отклонения первого из них, в то время как Promise.allSettled будет ждать, пока каждое обещание не будет разрешено или отклонено, поэтому вы можете взглянуть на Promise.allSettled, который не бросает на отказы.
Для целей этой истории я буду использовать несколько примеров методов:
res(ms)
— это функция, которая принимает целое число миллисекунд и возвращает обещание, которое разрешается после этого количества миллисекунд.rej(ms)
— это функция, которая принимает целое число миллисекунд и возвращает промис, который отклоняется после этого количества миллисекунд.
Звонок res
запускает таймер. Использование Promise.all
для ожидания нескольких задержек разрешится после завершения всех задержек, но помните, что они выполняются одновременно:
Пример №1
const data = await Promise.all([res(3000), res(2000), res(1000)]);
// ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^
// delay 1 delay 2 delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O delay 2
// =========O delay 3
//
// =============================O Promise.all
Это означает, что Promise.all
разрешится с данными из внутренних промисов через 3 секунды.
Но, Promise.all
ведет себя как «сбой быстро»:
Пример #2
const data = await Promise.all([res(3000), res(2000), rej(1000)]);
// ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^
// delay 1 delay 2 delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O delay 2
// =========X delay 3
//
// =========X Promise.all
Если вместо этого вы используете async-await
, вам придется ждать последовательного разрешения каждого промиса, что может быть не так эффективно:
Пример №3
const delay1 = res(3000); const delay2 = res(2000); const delay3 = rej(1000);
const data1 = await delay1; const data2 = await delay2; const data3 = await delay3;
// ms ------1---------2---------3 // =============================O delay 1 // ===================O delay 2 // =========X delay 3 // // =============================X await
Важно учитывать использование async/await и избегать ненужного ожидания, чтобы повысить производительность вашего кода. В приведенном примере показано, что использование метода Promise.all
может обеспечить параллельное выполнение промисов и сократить общее время, необходимое для разрешения промисов.
Один из сценариев, в котором лучше избегать использования async/await, — это когда результат одного промиса не зависит от результата другого промиса. В этом случае было бы более эффективно выполнять промисы одновременно, используя Promise.all
или аналогичный метод, а не ждать последовательного разрешения каждого промиса с помощью async/await.
Кроме того, важно учитывать «быстрое сбой» поведения Promise.all
, которое будет отклонено, как только одно из промисов в массиве будет отклонено. Напротив, использование async/await заставит код ждать разрешения или отклонения всех промисов, прежде чем двигаться дальше. В некоторых случаях может быть более подходящим использовать Promise.allSettled
, который будет ждать разрешения или отклонения всех промисов перед тем, как двигаться дальше, и не выдавать отказы.
Также важно учитывать потенциальное влияние на производительность при использовании async/await в цикле. В этом случае может быть более эффективно использовать цикл for...of
, а не цикл for...in
, так как последний потребует использования await
и будет выполнять промисы последовательно.
В заключение, хотя async/await может обеспечить чистый и удобный способ управления асинхронным поведением, важно учитывать его использование и потенциальное влияние на производительность. В некоторых случаях использование Promise.all
или подобного метода может оказаться более эффективным и повысить производительность вашего кода.
Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .
Заинтересованы в масштабировании запуска вашего программного обеспечения? Ознакомьтесь с разделом Схема.