Erster Unterschied - schnell scheitern
Ich stimme der Antwort von @ zzzzBov zu, aber der Vorteil von Promise.all ist nicht nur der einzige Unterschied. Einige Benutzer in Kommentaren fragen, warum Promise.all verwendet werden soll, wenn es nur im negativen Szenario schneller ist (wenn eine Aufgabe fehlschlägt). Und ich frage warum nicht? Wenn ich zwei unabhängige asynchrone parallele Aufgaben habe und die erste in sehr langer Zeit gelöst wird, die zweite jedoch in sehr kurzer Zeit abgelehnt wird, warum sollte der Benutzer auf die Fehlermeldung "sehr lange Zeit" anstatt "sehr kurze Zeit" warten? In realen Anwendungen müssen wir ein negatives Szenario berücksichtigen. Aber OK - in diesem ersten Unterschied können Sie entscheiden, welche Alternative Promise.all oder mehrere verwenden sollen.
Zweiter Unterschied - Fehlerbehandlung
Wenn Sie jedoch über eine Fehlerbehandlung nachdenken, MÜSSEN SIE Promise.all verwenden. Es ist nicht möglich, Fehler von asynchronen parallelen Aufgaben, die mit mehreren Wartezeiten ausgelöst wurden, korrekt zu behandeln. Im negativen Szenario enden Sie immer mit UnhandledPromiseRejectionWarning
und PromiseRejectionHandledWarning
obwohl Sie try / catch überall verwenden. Deshalb wurde Promise.all entwickelt. Natürlich könnte jemand sagen, dass wir diese Fehler mit process.on('unhandledRejection', err => {})
und unterdrücken könnenprocess.on('rejectionHandled', err => {})
aber es ist keine gute Praxis. Ich habe im Internet viele Beispiele gefunden, bei denen die Fehlerbehandlung für zwei oder mehr unabhängige asynchrone parallele Aufgaben überhaupt nicht oder nur in falscher Weise berücksichtigt wird - nur mit try / catch und in der Hoffnung, dass Fehler abgefangen werden. Es ist fast unmöglich, eine gute Praxis zu finden. Deshalb schreibe ich diese Antwort.
Zusammenfassung
Verwenden Sie niemals mehrere Wartezeiten für zwei oder mehr unabhängige asynchrone parallele Aufgaben, da Sie Fehler nicht ernsthaft behandeln können. Verwenden Sie für diesen Anwendungsfall immer Promise.all ().
Async / await ist kein Ersatz für Promises. Es ist nur eine hübsche Art, Versprechen zu verwenden ... asynchroner Code ist im Synchronisierungsstil geschrieben und wir können mehrere vermeidenthen
Versprechen .
Einige Leute sagen, dass wir mit Promise.all () Aufgabenfehler nicht separat behandeln können, sondern nur Fehler aus dem ersten abgelehnten Versprechen (ja, einige Anwendungsfälle erfordern möglicherweise eine separate Behandlung, z. B. für die Protokollierung). Es ist kein Problem - siehe "Addition" unten.
Beispiele
Betrachten Sie diese asynchrone Aufgabe ...
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)
});
};
Wenn Sie Aufgaben in einem positiven Szenario ausführen, gibt es keinen Unterschied zwischen Promise.all und Multiple Wait. Beide Beispiele enden Task 1 succeed! Task 2 succeed!
nach 5 Sekunden.
// 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!
Wenn die erste Aufgabe im positiven Szenario 10 Sekunden und die zweite Aufgabe im negativen Szenario 5 Sekunden dauert, gibt es Unterschiede bei den ausgegebenen Fehlern.
// 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!
Wir sollten hier bereits bemerken, dass wir etwas falsch machen, wenn wir mehrere Wartezeiten parallel verwenden. Um Fehler zu vermeiden, sollten wir natürlich damit umgehen! Lass es uns versuchen...
// 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!
Wie Sie sehen können, müssen wir nur einen Fang zur run
Funktion hinzufügen, und der Code mit der Fanglogik befindet sich im Rückruf ( asynchroner Stil ), um Fehler erfolgreich zu behandeln . Wir brauchen keine Fehler innerhalb der run
Funktion zu behandeln, da die asynchrone Funktion automatisch ausgeführt wird. Das Versprechen der Zurückweisung der task
Funktion führt zur Zurückweisung der run
Funktion. Um Rückrufe zu vermeiden, können wir den Synchronisierungsstil (async / await + try / catch) verwenden. try { await run(); } catch(err) { }
In diesem Beispiel ist dies jedoch nicht möglich, da wir ihn nicht await
im Hauptthread verwenden können. Er kann nur in der asynchronen Funktion verwendet werden (logisch, weil niemand dies möchte Hauptgewinde blockieren). So testen Sie, ob die Behandlung im Synchronisierungsstil , können wir aufrufenrun
von einer anderen asynchronen Funktion aus funktioniert, oder verwenden Sie IIFE (Sofort aufgerufener Funktionsausdruck) : (async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
.
Dies ist nur eine korrekte Methode, um zwei oder mehr asynchrone parallele Aufgaben auszuführen und Fehler zu behandeln. Sie sollten Beispiele unten vermeiden.
// 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);
};
Wir können versuchen, Code auf verschiedene Arten zu verarbeiten ...
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
... nichts wurde abgefangen, weil es Synchronisationscode verarbeitet, aber run
asynchron ist
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? Wir sehen erstens, dass der Fehler für Aufgabe 2 nicht behandelt wurde und später abgefangen wurde. Irreführend und immer noch voller Fehler in der Konsole. Auf diese Weise nicht verwendbar.
(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)
... das gleiche wie oben. Benutzer @Qwerty fragte in seiner gelöschten Antwort nach diesem seltsamen Verhalten, das anscheinend abgefangen wird, aber es gibt auch nicht behandelte Fehler. Wir fangen einen Fehler ab, weil run () online mit dem Schlüsselwort await abgelehnt wird und beim Aufruf von run () mit try / catch abgefangen werden kann. Wir erhalten auch einen nicht behandelten Fehler, weil wir die asynchrone Taskfunktion synchron aufrufen (ohne das Schlüsselwort wait) und diese Task außerhalb der Funktion run () ausgeführt wird und auch außerhalb fehlschlägt. Es ist ähnlich, wenn wir beim Aufrufen einer Synchronisierungsfunktion, deren Codeteil in setTimeout ausgeführt wird, nicht in der Lage sind, Fehler durch try / catch zu behandeln 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)
... "nur" zwei Fehler (dritter fehlt) aber nichts abgefangen.
Ergänzung (Behandeln Sie Aufgabenfehler separat und auch First-Fail-Fehler)
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!
... beachten Sie, dass ich in diesem Beispiel für beide Aufgaben negativeScenario = true verwendet habe, um besser zu demonstrieren, was passiert ( throw err
wird verwendet, um den endgültigen Fehler auszulösen).