Перша різниця - швидко провалитися
Я погоджуюся з відповіддю @ zzzzBov, але перевага Promise.all - не лише одна різниця. Деякі користувачі в коментарях запитують, навіщо використовувати Promise.all, коли це лише швидше за негативного сценарію (коли деякі завдання не вдається). І я запитую, чому ні? Якщо у мене є дві незалежні паралельні задачі асинхронізації, і перша вирішується за дуже довгий час, але друга відхиляється за дуже короткий час, чому слід залишати користувачеві чекати повідомлення про помилку "дуже довго" замість "дуже короткого часу"? У реальних програмах ми повинні враховувати негативний сценарій. Але добре - у цій першій різниці ви можете вирішити, яку альтернативу використовувати Promise.all проти кількох очікуючих.
Друга відмінність - обробка помилок
Але при розгляді помилок з ВАС МОЖЕТЕ використовувати Promise.all. Неможливо правильно впоратися з помилками асинхронізації паралельних завдань, спровокованих багаторазовим очікуванням. У негативному сценарії ви завжди будете закінчуватися, UnhandledPromiseRejectionWarning
і PromiseRejectionHandledWarning
хоча ви використовуєте пробувати / ловити будь-де. Саме тому Promise.all був розроблений. Звичайно , хто - то може сказати , що ми можемо придушити , що помилки з допомогою process.on('unhandledRejection', err => {})
і , process.on('rejectionHandled', err => {})
але це не є доброю практикою. В Інтернеті я знайшов багато прикладів, які взагалі не розглядають помилки для двох чи більше незалежних паралельних завдань асинхронізації або розглядають її, але неправильно - просто використовую спробу / ловити та сподіваюся, що вона виявить помилки. Практичну практику знайти майже неможливо. Ось чому я пишу цю відповідь.
Підсумок
Ніколи не використовуйте кілька функцій очікування для двох або більше незалежних завдань паралельної асинхронізації, оскільки ви не зможете серйозно обробляти помилки. Завжди використовуйте Promise.all () для цього випадку використання.
Асинхронізація / очікування - це не заміна обіцянок. Це просто гарний спосіб використання обіцянок ... Асинхронний код написаний у стилі синхронізації, і ми можемо уникнути декількох 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
функціонування, а код з логікою ловлі знаходиться в зворотному дзвінку ( асинхронний стиль ). Нам не потрібні помилки обробки всередині run
функції, оскільки функція асинхронізації виконується автоматично - обіцянка відхилення task
функції викликає відхилення run
функції. Щоб уникнути зворотного дзвінка, ми можемо використовувати стиль синхронізації (async / await + try / catch), try { await run(); } catch(err) { }
але в цьому прикладі це неможливо, оскільки ми не можемо використовувати await
в основному потоці - він може використовуватися лише у функції async (це логічно, тому що ніхто не хоче блокувати головну нитку). Щоб перевірити, чи працює обробка в стилі синхронізації, ми можемо зателефонувати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 () відхиляється в рядку з ключовим словом очікуванням і може бути перехоплена за допомогою try / catch при виклику run (). Ми також отримуємо необроблену помилку, оскільки ми викликаємо функцію завдання async синхронно (без очікування ключового слова), і це завдання працює поза функцією 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)
... "лише" дві помилки (3-я одна відсутня), але нічого не застало.
Доповнення (обробляти помилки завдання окремо, а також помилку, що не відбулася)
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!
... зауважте, що в цьому прикладі я використовував negativeScenario = true для обох завдань для кращої демонстрації того, що відбувається ( throw err
використовується для запуску остаточної помилки)