Prima differenza: fallisci velocemente
Sono d'accordo con la risposta di @ zzzzBov, ma il vantaggio di "Fail fast fast" di Promise.all non è solo la differenza. Alcuni utenti nei commenti chiedono perché utilizzare Promise.all quando è solo più veloce in uno scenario negativo (quando alcune attività falliscono). E chiedo perché no? Se ho due attività parallele asincrone indipendenti e la prima viene risolta in un tempo molto lungo ma la seconda viene rifiutata in brevissimo tempo perché lasciare all'utente attendere un messaggio di errore "molto lungo" anziché "molto breve"? Nelle applicazioni della vita reale dobbiamo considerare uno scenario negativo. Ma OK - in questa prima differenza puoi decidere quale alternativa utilizzare Promise.all rispetto a più aspetti.
Seconda differenza: gestione degli errori
Ma quando si considera la gestione degli errori DEVI usare Promise.all. Non è possibile gestire correttamente gli errori di attività parallele asincrone attivate con più attesa. In uno scenario negativo finirai sempre con UnhandledPromiseRejectionWarning
ePromiseRejectionHandledWarning
sebbene tu usi try / catch ovunque. Ecco perché è stato progettato Promise.all. Certo qualcuno potrebbe dire che possiamo sopprimere gli errori usando process.on('unhandledRejection', err => {})
e process.on('rejectionHandled', err => {})
ma non è una buona pratica. Ho trovato molti esempi su Internet che non considerano affatto la gestione degli errori per due o più attività parallele asincrone indipendenti o la considerano, ma in modo errato: basta usare try / catch e sperare che rilevi errori. È quasi impossibile trovare buone pratiche. Ecco perché sto scrivendo questa risposta.
Sommario
Non utilizzare mai più wait per due o più attività parallele asincrone indipendenti perché non sarai in grado di gestire seriamente gli errori. Utilizzare sempre Promise.all () per questo caso d'uso.
Async / await non sostituisce Promises. È semplicemente un modo carino per usare le promesse ... il codice asincrono è scritto in stile sync e possiamo evitarne il multiplothen
nelle promesse.
Alcune persone affermano che utilizzando Promise.all () non possiamo gestire gli errori delle attività separatamente ma solo l'errore della prima promessa respinta (sì, alcuni casi d'uso potrebbero richiedere una gestione separata, ad esempio per la registrazione). Non è un problema - vedi sotto "Aggiunta" sotto.
Esempi
Considera questa attività asincrona ...
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)
});
};
Quando si eseguono attività in uno scenario positivo, non vi è alcuna differenza tra Promise.all e più wait. Entrambi gli esempi terminano Task 1 succeed! Task 2 succeed!
dopo 5 secondi.
// 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!
Quando la prima attività richiede 10 secondi in uno scenario positivo e l'attività secondi richiede 5 secondi in uno scenario negativo, ci sono differenze negli errori emessi.
// 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!
Dovremmo già notare qui che stiamo facendo qualcosa di sbagliato quando si usano più aspetti in parallelo. Naturalmente per evitare errori dovremmo gestirlo! Proviamo...
// 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!
Come si può vedere per gestire correttamente l'errore, è necessario aggiungere solo un catch alla run
funzione e il codice con la logica catch è in callback ( stile asincrono ). Non abbiamo bisogno di gestire gli errori all'interno della run
funzione perché la funzione asincrona lo fa automaticamente - promettere il rifiuto della task
funzione provoca il rifiuto della run
funzione. Per evitare il callback possiamo usare lo stile di sincronizzazione (async / await + try / catch) try { await run(); } catch(err) { }
ma in questo esempio non è possibile perché non possiamo usare await
nel thread principale - può essere usato solo nella funzione asincrona (è logico perché nessuno vuole bloccare il filo principale). Per verificare se la gestione funziona nello stile di sincronizzazione, possiamo chiamarerun
Funzione da un'altra funzione asincrona o uso IIFE (Subito Richiamato espressione di funzione): (async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
.
Questo è solo un modo corretto per eseguire due o più attività parallele asincrone e gestire gli errori. Dovresti evitare esempi di seguito.
// 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);
};
Possiamo provare a gestire il codice sopra diversi modi ...
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
... nulla è stato rilevato perché gestisce il codice di sincronizzazione ma run
è asincrono
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? Innanzitutto vediamo che l'errore per l'attività 2 non è stato gestito e successivamente è stato rilevato. Ingannevole e ancora pieno di errori nella console. Inutilizzabile in questo modo.
(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)
... come sopra. L'utente @Qwerty nella sua risposta eliminata ha chiesto di questo strano comportamento che sembra essere stato colto ma ci sono anche errori non gestiti. Si rileva un errore perché run () viene rifiutato in linea con la parola chiave wait e può essere rilevato utilizzando try / catch quando si chiama run (). Riceviamo anche errori non gestiti perché stiamo chiamando la funzione di attività asincrona in modo sincrono (senza la parola chiave wait) e questa attività viene eseguita al di fuori della funzione run () e fallisce anche all'esterno. E 'simile quando non siamo in grado di gestire errore try / catch quando si chiama una funzione di sincronizzazione quale parte di piste di codice in 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)
... "solo" due errori (manca il terzo) ma non è stato rilevato nulla.
Aggiunta (gestire gli errori dell'attività separatamente e anche errore first-fail)
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!
... nota che in questo esempio ho usato negativeScenario = true per entrambe le attività per una migliore dimostrazione di ciò che accade ( throw err
viene utilizzato per generare l'errore finale)