Есть ли разница между:
const [result1, result2] = await Promise.all([task1(), task2()]);
и
const t1 = task1();
const t2 = task2();
const result1 = await t1;
const result2 = await t2;
и
const [t1, t2] = [task1(), task2()];
const [result1, result2] = [await t1, await t2];
javascript
async-await
скрытый
источник
источник
Ответы:
Для целей этого ответа я буду использовать несколько примеров методов:
res(ms)
это функция, которая принимает целое число миллисекунд и возвращает обещание, которое разрешается через столько миллисекунд.rej(ms)
это функция, которая принимает целое число миллисекунд и возвращает обещание, которое отклоняется через столько миллисекунд.Вызов
Пример № 1res
запускает таймер. ИспользованиеPromise.all
для ожидания нескольких задержек разрешится после того, как все задержки закончились, но помните, что они выполняются одновременно:Показать фрагмент кода
Это означает, что
Promise.all
разрешится с данными из внутренних обещаний через 3 секунды.Но
Пример № 2Promise.all
имеет поведение "быстро провал" :Показать фрагмент кода
Если вы используете
Пример № 3async-await
вместо этого, вам придется ждать каждого обещания для разрешения последовательно, что может быть не столь эффективно:Показать фрагмент кода
источник
unhandledrejection
ошибки. Вы никогда не захотите использовать это. Пожалуйста, добавьте это к своему ответу.Первое отличие - быстро провалиться
Я согласен с ответом @ zzzzBov, но преимущество Promise в «быстром провале» - не единственное отличие. Некоторые пользователи в комментариях спрашивают, зачем использовать Promise.all, если он работает быстрее при отрицательном сценарии (когда какая-то задача не выполняется). И я спрашиваю, почему нет? Если у меня есть две независимые асинхронные параллельные задачи, и первая решается за очень долгое время, а вторая отклоняется за очень короткое время, зачем оставлять пользователю ждать сообщения об ошибке «очень долгое время» вместо «очень короткого времени»? В реальных приложениях мы должны учитывать негативный сценарий. Но хорошо - в этом первом разнице вы можете решить, какую альтернативу использовать Promise.all вместо нескольких ожидающих.
Второе отличие - обработка ошибок
Но при рассмотрении обработки ошибок ВЫ ДОЛЖНЫ использовать Promise.all. Невозможно правильно обрабатывать ошибки асинхронных параллельных задач, вызванных множественным ожиданием. В негативном сценарии вы всегда будете заканчиваться,
UnhandledPromiseRejectionWarning
иPromiseRejectionHandledWarning
хотя вы используете где-нибудь try / catch. Вот почему Promise.all был разработан. Конечно , кто - то может сказать , что мы можем подавить , что ошибки с помощьюprocess.on('unhandledRejection', err => {})
и ,process.on('rejectionHandled', err => {})
но это не является хорошей практикой. Я нашел много примеров в интернете, которые вообще не рассматривают обработку ошибок для двух или более независимых асинхронных параллельных задач или рассматривают это, но неверным образом - просто используя try / catch и надеясь, что он поймает ошибки. Практически невозможно найти хорошую практику. Вот почему я пишу этот ответ.Резюме
Никогда не используйте множественное ожидание для двух или более независимых асинхронных параллельных задач, потому что вы не сможете серьезно обрабатывать ошибки. Всегда используйте Promise.all () для этого варианта использования. Async / await не является заменой для Promises. Это просто красивый способ использования обещаний ... асинхронный код написан в стиле синхронизации, и мы можем избежать многократных
then
обещаний.Некоторые люди говорят, что с помощью Promise.all () мы не можем обрабатывать ошибки задач отдельно, а только ошибки из первого отклоненного обещания (да, в некоторых случаях может потребоваться отдельная обработка, например, для ведения журнала). Это не проблема - см. Раздел «Дополнение» ниже.
Примеры
Рассмотрим эту асинхронную задачу ...
При выполнении задач в положительном сценарии нет разницы между Promise.all и несколькими ожидающими. Оба примера заканчиваются
Task 1 succeed! Task 2 succeed!
через 5 секунд.Когда первая задача занимает 10 секунд в положительном сценарии, а секундная задача занимает 5 секунд в отрицательном сценарии, появляются различия в ошибках.
Мы уже должны заметить, что мы делаем что-то не так при параллельном использовании нескольких ожидающих. Конечно, чтобы избежать ошибок, мы должны справиться с этим! Давай попробуем...
Как вы можете видеть, чтобы успешно обрабатывать ошибки, нам нужно добавить в
run
функцию только один catch, а код с логикой catch находится в режиме обратного вызова ( асинхронный стиль ). Нам не нужно обрабатывать ошибки внутриrun
функции, потому что асинхронная функция делает это автоматически - обещание отклоненияtask
функции вызывает отклонениеrun
функции. Чтобы избежать обратного вызова, мы можем использовать стиль синхронизации (async / await + try / catch),try { await run(); } catch(err) { }
но в этом примере это невозможно, потому что мы не можем использовать егоawait
в основном потоке - его можно использовать только в асинхронной функции (это логично, потому что никто не хочет заблокировать основной поток). Чтобы проверить, работает ли обработка в стиле синхронизации, мы можем вызватьrun
функция от другой функции асинхронной или использовать IIFE (Сразу Вызывается функция Expression):(async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
.Это только один правильный способ запуска двух или более асинхронных параллельных задач и обработки ошибок. Вам следует избегать примеров ниже.
Мы можем попытаться обработать код несколькими способами ...
... ничего не поймано, потому что он обрабатывает код синхронизации, но
run
работает асинхронно... Wtf? Во-первых, мы видим, что ошибка для задачи 2 не была обработана, а затем была обнаружена. Вводит в заблуждение и все еще полно ошибок в консоли. Не подходит для этого пути.
... так же, как и выше. Пользователь @Qwerty в своем удаленном ответе спросил об этом странном поведении, которое кажется перехваченным, но есть и необработанные ошибки. Мы отлавливаем ошибку, потому что run () отклоняется в строке с ключевым словом await и может быть перехвачен с помощью try / catch при вызове run (). Мы также получаем необработанную ошибку, потому что мы вызываем асинхронную функцию задачи синхронно (без ключевого слова await), и эта задача выполняется вне функции run (), а также выходит из строя снаружи. Это похоже , когда мы не в состоянии справиться ошибку Try / улове при вызове некоторой функции синхронизации , какая часть кода работает в SetTimeout ...
function test() { setTimeout(function() { console.log(causesError); }, 0); }; try { test(); } catch(e) { /* this will never catch error */ }
.... "только" две ошибки (третья отсутствует), но ничего не поймано.
Дополнение (обрабатывать ошибки задачи отдельно, а также ошибку первого сбоя)
... обратите внимание, что в этом примере для обеих задач я использовал отрицательный сценарий = true, чтобы лучше продемонстрировать, что происходит (
throw err
используется для генерации последней ошибки)источник
Как правило, использование
Promise.all()
запускает запросы "async" параллельно. Использованиеawait
может работать параллельно ИЛИ блокировать синхронизацию.Функции test1 и test2 ниже показывают, как
await
можно выполнять асинхронную или синхронизацию.test3 показывает,
Promise.all()
что это асинхронный.jsfiddle с результатами по времени - откройте консоль браузера, чтобы увидеть результаты теста
Синхронизировать поведение. НЕ работает параллельно, занимает ~ 1800 мс :
Асинхронное поведение. Работает в паралеле, занимает ~ 600 мс :
Асинхронное поведение. Работает параллельно, занимает ~ 600 мс :
TLDR; Если вы используете
Promise.all()
его также будет «быстрый сбой» - прекратите работу во время первого сбоя любой из включенных функций.источник
Вы можете проверить сами.
В этой скрипке я провел тест, чтобы продемонстрировать блокирующую природу
await
, в противоположностьPromise.all
которой начнутся все обещания, и пока одно ожидает, оно продолжится с другими.источник
t1 = task1(); t2 = task2()
и последующим использованиемawait
для них обоих,result1 = await t1; result2 = await t2;
как в его вопросе, в отличие от того, что вы тестируете, которое используетawait
исходный вызов, какresult1 = await task1(); result2 = await task2();
. Код в его вопросе запускает все обещания сразу. Разница, как показывает ответ, состоит в том, что сбои будут сообщаться быстрееPromise.all
.В случае ожидания Promise.all ([task1 (), task2 ()]); «task1 ()» и «task2 ()» будут работать параллельно и будут ждать, пока оба обещания не будут выполнены (разрешены или отклонены). Тогда как в случае
t2 будет работать только после того, как t1 завершит выполнение (было разрешено или отклонено). И t1, и t2 не будут работать параллельно.
источник