Первое отличие - быстро провалиться
Я согласен с ответом @ 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 () мы не можем обрабатывать ошибки задач отдельно, а только ошибки из первого отклоненного обещания (да, в некоторых случаях может потребоваться отдельная обработка, например, для ведения журнала). Это не проблема - см. Раздел «Дополнение» ниже.
Примеры
Рассмотрим эту асинхронную задачу ...
const task = function(taskNum, seconds, negativeScenario) {
return new Promise((resolve, reject) => {
setTimeout(_ => {
if (negativeScenario)
reject(new Error('Task ' + taskNum + ' failed!'));
else
resolve('Task ' + taskNum + ' succeed!');
}, seconds * 1000)
});
};
При выполнении задач в положительном сценарии нет разницы между Promise.all и несколькими ожидающими. Оба примера заканчиваются Task 1 succeed! Task 2 succeed!
через 5 секунд.
// Promise.all alternative
const run = async function() {
// tasks run immediate in parallel and wait for both results
let [r1, r2] = await Promise.all([
task(1, 5, false),
task(2, 5, false)
]);
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: Task 1 succeed! Task 2 succeed!
// multiple await alternative
const run = async function() {
// tasks run immediate in parallel
let t1 = task(1, 5, false);
let t2 = task(2, 5, false);
// wait for both results
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: Task 1 succeed! Task 2 succeed!
Когда первая задача занимает 10 секунд в положительном сценарии, а секундная задача занимает 5 секунд в отрицательном сценарии, появляются различия в ошибках.
// Promise.all alternative
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, false),
task(2, 5, true)
]);
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// multiple await alternative
const run = async function() {
let t1 = task(1, 10, false);
let t2 = task(2, 5, true);
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
// at 10th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
Мы уже должны заметить, что мы делаем что-то не так при параллельном использовании нескольких ожидающих. Конечно, чтобы избежать ошибок, мы должны справиться с этим! Давай попробуем...
// Promise.all alternative
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, false),
task(2, 5, true)
]);
console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: Caught error Error: Task 2 failed!
Как вы можете видеть, чтобы успешно обрабатывать ошибки, нам нужно добавить в 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); }; })();
.
Это только один правильный способ запуска двух или более асинхронных параллельных задач и обработки ошибок. Вам следует избегать примеров ниже.
// multiple await alternative
const run = async function() {
let t1 = task(1, 10, false);
let t2 = task(2, 5, true);
let r1 = await t1;
let r2 = await t2;
console.log(r1 + ' ' + r2);
};
Мы можем попытаться обработать код несколькими способами ...
try { run(); } catch(err) { console.log('Caught error', err); };
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled
... ничего не поймано, потому что он обрабатывает код синхронизации, но run
работает асинхронно
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: Caught error Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
... Wtf? Во-первых, мы видим, что ошибка для задачи 2 не была обработана, а затем была обнаружена. Вводит в заблуждение и все еще полно ошибок в консоли. Не подходит для этого пути.
(async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: Caught error Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
... так же, как и выше. Пользователь @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 */ }
.
const run = async function() {
try {
let t1 = task(1, 10, false);
let t2 = task(2, 5, true);
let r1 = await t1;
let r2 = await t2;
}
catch (err) {
return new Error(err);
}
console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
... "только" две ошибки (третья отсутствует), но ничего не поймано.
Дополнение (обрабатывать ошибки задачи отдельно, а также ошибку первого сбоя)
const run = async function() {
let [r1, r2] = await Promise.all([
task(1, 10, true).catch(err => { console.log('Task 1 failed!'); throw err; }),
task(2, 5, true).catch(err => { console.log('Task 2 failed!'); throw err; })
]);
console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Run failed (does not matter which task)!'); });
// at 5th sec: Task 2 failed!
// at 5th sec: Run failed (does not matter which task)!
// at 10th sec: Task 1 failed!
... обратите внимание, что в этом примере для обеих задач я использовал отрицательный сценарий = true, чтобы лучше продемонстрировать, что происходит ( throw err
используется для генерации последней ошибки)