355 votes

Une différence entre wait Promise.all () et multiple wait?

Y a-t-il une différence entre:

 const [result1, result2] = await Promise.all([task1(), task2()]);
 

et

 const t1 = task1();
const t2 = task2();

const result1 = await t1;
const result2 = await t2;
 

et

 const [t1, t2] = [task1(), task2()];
const [result1, result2] = [await t1, await t2];
 

373voto

zzzzBov Points 62084

Pour les fins de cette réponse, je vais être à l'aide de quelques exemples de méthodes:

  • res(ms) est une fonction qui prend un entier de millisecondes, et retourne une promesse qui résout après que le nombre de millisecondes.
  • rej(ms) est une fonction qui prend un entier de millisecondes, et retourne une promesse qui rejette après que le nombre de millisecondes.

Appelant res commence la minuterie. À l'aide de Promise.all à attendre pour une poignée de retards résoudre à la suite de tous les retards ont fini, mais n'oubliez pas qu'ils ne s'exécutent en même temps:

Exemple #1
const data = await Promise.all([res(3000), res(2000), res(1000)])
//                              ^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^
//                               delay 1    delay 2    delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O           delay 2
// =========O                     delay 3
//
// =============================O Promise.all

async function example() {
  const start = Date.now()
  let i = 0
  function res(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
        console.log(`res #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }

  const data = await Promise.all([res(3000), res(2000), res(1000)])
  console.log(`Promise.all finished`, Date.now() - start)
}

example()

Cela signifie qu' Promise.all permettra de résoudre avec les données de l'intérieur promet au bout de 3 secondes.

Mais, Promise.all a un "fail fast" comportement:

Exemple #2
const data = await Promise.all([res(3000), res(2000), rej(1000)])
//                              ^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^
//                               delay 1    delay 2    delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O           delay 2
// =========X                     delay 3
//
// =========X                     Promise.all

async function example() {
  const start = Date.now()
  let i = 0
  function res(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
        console.log(`res #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  function rej(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject()
        console.log(`rej #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  try {
    const data = await Promise.all([res(3000), res(2000), rej(1000)])
  } catch (error) {
    console.log(`Promise.all finished`, Date.now() - start)
  }
}

example()

Si vous utilisez async-await au lieu de cela, vous aurez à attendre pour chaque promesse de résoudre de manière séquentielle, ce qui peut ne pas être aussi efficace:

Exemple #3
const delay1 = res(3000)
const delay2 = res(2000)
const delay3 = rej(1000)

const data1 = await delay1
const data2 = await delay2
const data3 = await delay3

// ms ------1---------2---------3
// =============================O delay 1
// ===================O           delay 2
// =========X                     delay 3
//
// =============================X await

async function example() {
  const start = Date.now()
  let i = 0
  function res(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
        console.log(`res #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  function rej(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject()
        console.log(`rej #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  try {
    const delay1 = res(3000)
    const delay2 = res(2000)
    const delay3 = rej(1000)

    const data1 = await delay1
    const data2 = await delay2
    const data3 = await delay3
  } catch (error) {
    console.log(`await finished`, Date.now() - start)
  }
}

example()

266voto

mikep Points 956

Première différence de l'échec rapide

Je suis d'accord avec @zzzzBov de la réponse, mais "fail fast" l'avantage de la Promesse.tout n'est pas seulement une différence. Certains utilisateurs dans les commentaires se demande pourquoi pour utilisation Promesse.lorsqu'il est seulement plus rapide dans le scénario négatif (quand une tâche échoue). Et je me demande pourquoi pas? Si j'ai deux indépendants async et de tâches parallèles premier est résolu dans très longtemps, mais la deuxième est rejeté dans très peu de temps pourquoi laisser l'utilisateur d'attendre mesage d'erreur "très longtemps" au lieu de "très court laps de temps"? Dans des applications réelles, nous devons considérer scénario négatif. Mais OK - dans cette première différence, vous pouvez décider alternative à l'utilisation de la Promesse.tous vs multiples vous attendent.

Deuxième différence d'erreur de manipulation

Mais lorsque l'on considère la gestion d'erreur, VOUS DEVEZ utiliser Promesse.tous les. Il n'est pas possible de gérer correctement les erreurs de async tâches en parallèle déclenchée avec de multiples vous attendent. Dans le scénario négatif, vous allez toujours à la fin avec UnhandledPromiseRejectionWarning et PromiseRejectionHandledWarning même si vous utiliser try/catch n'importe où. C'est pourquoi la Promesse.tout a été conçu. Bien entendu, quelqu'un pourrait dire qu'on peut supprimer que les erreurs à l'aide de process.on('unhandledRejection', err => {}) et process.on('rejectionHandled', err => {}) mais il n'est pas une bonne pratique. J'ai trouvé de nombreux exemples sur internet qui ne considère pas de gestion d'erreur pour deux ou plus indépendants async tâches en parallèle à tous ou à envisager, mais dans le mauvais sens - juste à l'aide de try/catch et en espérant qu'il va rattraper les erreurs. Il est presque impossible de trouver une bonne pratique. C'est pourquoi je suis en train d'écrire cette réponse.

Résumé

Ne jamais utiliser de multiples attendent à deux ou plus indépendants async tâches en parallèle parce que vous ne sera pas en mesure de gérer les erreurs au sérieux. Utilisez toujours des Promesses.tous les() pour ce cas d'utilisation. Async/await n'est pas de remplacement pour des Promesses. Il est juste assez de la manière d'utiliser des promesses... async code est écrit dans la synchronisation de style et on peut éviter de multiples then dans les promesses.

Certaines personnes disent que l'utilisation de la Promesse.tous les() nous ne pouvons pas gérer les tâches erreurs séparément mais seulement d'erreur de première rejeté promesse (oui, certains cas peuvent nécessiter de traitement distincte par exemple, pour l'enregistrement). Il n'est pas un problème - voir "Plus" de la rubrique ci-dessous.

Exemples

Considérer 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 le scénario positif il n'y a pas de différence entre les Promesses.tous et plusieurs attendent. Les deux exemples de la fin avec 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!

Lors de la première tâche prend 10 secondes dans le scénario positif et secondes tâche prend que 5 secondes dans le 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 attendent en parallèle. Bien sûr, pour éviter des erreurs, nous devons le gérer! Laissez-vous tenter...


// 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 avec succès d'erreur nous avons besoin d'ajouter des captures d' run de la fonction et le code avec des captures logique est dans le rappel (async style). Nous n'avons pas besoin de gérer les erreurs à l'intérieur d' run fonctionner, car la fonction async il le fait automatiquement - la promesse de rejet de l' task fonction provoque le rejet de l' run fonction. Pour éviter de rappel, nous pouvons utiliser la synchronisation de style (async/await + try/catch) try { await run(); } catch(err) { } , mais dans cet exemple, il n'est pas possible car nous ne pouvons pas utiliser await dans le thread principal peut être utilisé uniquement en fonction async (c'est logique parce que personne ne veut bloquer le thread principal). Pour tester si la manipulation fonctionne en synchronisation de style , nous pouvons l'appeler run fonction à partir d'une autre fonction async ou de l'utilisation IIFE (Immédiatement appelé la Fonction de l'Expression): (async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();.

C'est une seule bonne façon d'exécuter deux ou plusieurs async tâches en parallèle et 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 manipuler le code au-dessus de plusieurs façons...

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é pris car il gère la synchronisation de code, 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 d'abord que l'erreur pour la tâche 2 n'a pas été manipulés et, plus tard, qui a été attrapé. Trompeuse et encore 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.

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 (3e est manquant) mais rien attrapé.


Plus (gérer les erreurs de tâches séparément et aussi la première de l'échec d'erreur)

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 le feu final d'erreur)

9voto

zpr Points 333

Vous pouvez vérifier par vous-même.

Dans ce violon , j’ai fait un test pour démontrer la nature bloquante de await , par opposition à Promise.all qui va commencer toutes les promesses et en attendant il va continuer avec le autres.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X