Première différence - échouer rapidement
Je suis d'accord avec la réponse de @ zzzzBov mais l'avantage "échouer rapidement" de Promise.all n'est pas seulement la seule différence. Certains utilisateurs dans les commentaires demandent pourquoi utiliser Promise.all alors qu'il n'est plus rapide que dans un scénario négatif (lorsqu'une tâche échoue). Et je demande pourquoi pas? Si j'ai deux tâches parallèles asynchrones indépendantes et que la première est résolue dans un temps très long, mais la seconde est rejetée dans un délai très court, pourquoi laisser l'utilisateur attendre le message d'erreur "très long temps" au lieu de "très court temps"? Dans les applications réelles, nous devons envisager un scénario négatif. Mais OK - dans cette première différence, vous pouvez décider quelle alternative utiliser Promise.all par rapport à plusieurs attendent.
Deuxième différence - gestion des erreurs
Mais lorsque vous envisagez de gérer les erreurs, VOUS DEVEZ utiliser Promise.all. Il n'est pas possible de gérer correctement les erreurs des tâches parallèles asynchrones déclenchées avec plusieurs wait. Dans un scénario négatif, vous finirez toujours par UnhandledPromiseRejectionWarning
et PromiseRejectionHandledWarning
bien que vous utilisiez try / catch n'importe où. C'est pourquoi Promise.all a été conçu. Bien sûr, quelqu'un pourrait dire que nous pouvons supprimer les erreurs en utilisant process.on('unhandledRejection', err => {})
etprocess.on('rejectionHandled', err => {})
mais ce n'est pas une bonne pratique. J'ai trouvé de nombreux exemples sur Internet qui ne prennent pas du tout en compte la gestion des erreurs pour deux ou plusieurs tâches parallèles asynchrones indépendantes ou qui ne le considèrent pas mais de la mauvaise manière - en utilisant simplement try / catch et en espérant qu'il détectera les erreurs. Il est presque impossible de trouver de bonnes pratiques. C'est pourquoi j'écris cette réponse.
Résumé
N'utilisez jamais d'attente multiple pour deux ou plusieurs tâches parallèles asynchrones indépendantes car vous ne serez pas en mesure de gérer les erreurs sérieusement. Utilisez toujours Promise.all () pour ce cas d'utilisation.
Async / await ne remplace pas les promesses. C'est juste une jolie manière d'utiliser les promesses ... le code async est écrit dans le style de synchronisation et nous pouvons éviter plusieursthen
promesses.
Certaines personnes disent qu'en utilisant Promise.all (), nous ne pouvons pas gérer les erreurs de tâches séparément mais seulement l'erreur de la première promesse rejetée (oui, certains cas d'utilisation peuvent nécessiter une gestion séparée, par exemple pour la journalisation). Ce n'est pas un problème - voir l'en-tête "Ajout" ci-dessous.
Exemples
Considérez cette tâche asynchrone ...
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)
});
};
Lorsque vous exécutez des tâches dans un scénario positif, il n'y a aucune différence entre Promise.all et multiple await. Les deux exemples se terminent Task 1 succeed! Task 2 succeed!
après 5 secondes.
// 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!
Lorsque la première tâche prend 10 secondes dans un scénario positif et que la tâche secondes prend 5 secondes dans un scénario négatif, il existe des différences dans les erreurs émises.
// 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!
Nous devrions déjà remarquer ici que nous faisons quelque chose de mal lors de l'utilisation de plusieurs wait en parallèle. Bien sûr, pour éviter les erreurs, nous devons le gérer! Essayons...
// 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!
Comme vous pouvez le voir pour gérer correctement l'erreur, nous devons ajouter une seule capture à la run
fonction et le code avec la logique de capture est en rappel ( style asynchrone ). Nous n'avons pas besoin de gérer les erreurs à l'intérieur de la run
fonction car la fonction asynchrone le fait automatiquement - la promesse de rejet de la task
fonction entraîne le rejet de la run
fonction. Pour éviter le rappel, nous pouvons utiliser le style de synchronisation (async / await + try / catch) try { await run(); } catch(err) { }
mais dans cet exemple, ce n'est pas possible car nous ne pouvons pas l'utiliser await
dans le thread principal - il ne peut être utilisé que dans la fonction async (c'est logique car personne ne veut bloquer le thread principal). Pour tester si la gestion fonctionne dans le style de synchronisation, nous pouvons appelerrun
fonction d' une autre fonction asynchrone ou utilisation Ia vie (Expression immédiatement Appelé Function): (async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
.
Ce n'est qu'une façon correcte d'exécuter deux ou plusieurs tâches parallèles asynchrones et de gérer les erreurs. Vous devriez éviter les exemples ci-dessous.
// 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);
};
Nous pouvons essayer de gérer le code de plusieurs manières ...
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
... rien n'a été attrapé car il gère le code de synchronisation mais run
est asynchrone
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? Nous voyons tout d'abord que l'erreur de la tâche 2 n'a pas été gérée et plus tard qu'elle a été interceptée. Trompeur et toujours plein d'erreurs dans la console. Inutilisable de cette façon.
(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)
... le même que ci-dessus. L'utilisateur @Qwerty, dans sa réponse supprimée, a posé des questions sur ce comportement étrange qui semble être attrapé, mais il y a aussi des erreurs non gérées. Nous détectons l'erreur car run () est rejeté en ligne avec le mot-clé await et peut être détecté en utilisant try / catch lors de l'appel de run (). Nous obtenons également une erreur non gérée car nous appelons la fonction de tâche asynchrone de manière synchrone (sans mot-clé await) et cette tâche s'exécute en dehors de la fonction run () et échoue également à l'extérieur. Il est similaire quand nous ne sommes pas en mesure de gérer l' erreur par try / catch lorsque vous appelez une fonction de synchronisation qui partie de code se exécute dans 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)
... "seulement" deux erreurs (la troisième est manquante) mais rien n'a été détecté.
Ajout (gérer les erreurs de tâche séparément et également l'erreur de premier échec)
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!
... notez que dans cet exemple, j'ai utilisé negativeScenario = true pour les deux tâches pour une meilleure démonstration de ce qui se passe ( throw err
est utilisé pour déclencher l'erreur finale)