54 votes

En attente de plusieurs opérations d'attente simultanées

Comment puis-je modifier le code suivant afin que les deux opérations asynchrones soient déclenchées et aient la possibilité de s'exécuter simultanément?

 const value1 = await getValue1Async();
const value2 = await getValue2Async();
// use both values
 

Dois-je faire quelque chose comme ça?

 const p1 = getValue1Async();
const p2 = getValue2Async();
const value1 = await p1;
const value2 = await p2;
// use both values
 

76voto

T.J. Crowder Points 285826

TL;DR

N'utilisez pas le modèle en question où vous obtenez les promesses, et puis séparément attendre sur eux; au lieu de cela, utilisez Promise.all (au moins pour l'instant):

const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);

Alors que votre solution ne faire fonctionner les deux opérations en parallèle, il ne gère pas le rejet correctement si les deux promet de le rejeter.

Détails:

Votre solution fonctionne en parallèle, mais attend toujours le premier à terminer avant l'attente de la seconde. Si vous voulez juste pour démarrer, exécuter en parallèle, et d'obtenir les deux résultats, il suffit bien. (Non, ce n'est pas le cas, continuez à lire...) Notez que si le premier prend, disons, cinq secondes et le second échoue dans une seconde, votre code d'attendre les cinq secondes avant la faute.

Malheureusement, il n'y a pas actuellement de l' await de la syntaxe pour faire un parallèle attendre, alors vous avez l'embarras que vous avez énumérés, ou Promise.all. (Il y a eu une discussion d' await.all ou similaire, bien que, peut-être un jour.)

L' Promise.all version est:

const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);

...ce qui est plus concis, et aussi ne pas attendre que la première opération à réaliser si le second ne parvient pas rapidement (par exemple, dans mes cinq secondes / un deuxième exemple ci-dessus, au-dessus de la rejeter dans un deuxième plutôt que d'attendre cinq). Notez également qu'avec votre code d'origine, si la deuxième promesse rejette avant la première promesse se résout, vous pouvez ainsi obtenir un "non gérée rejet" erreur dans la console (vous n'avez actuellement avec Chrome v61), bien que cette erreur est sans doute fausse (parce que vous n', éventuellement, gérer le rejet). Mais si les deux promesses de rejeter, vous obtiendrez un véritable non gérée rejet d'erreur parce que le flux de contrôle n'atteint jamais l' const value2 = await p2; et donc la p2 rejet n'est jamais traitée.

Non gérée rejets sont une Mauvaise Chose™ (tant et si bien que bientôt, NodeJS va annuler le processus de vraiment non gérée rejet, tout comme les exceptions non gérées — parce que c'est ce qu'ils sont), donc il vaut mieux éviter les "obtenir la promesse alors await il" motif dans votre question.

Voici un exemple de la différence de timing dans le cas d'échec (à l'aide de 500ms et 100ms plutôt que 5 secondes et 1 seconde), et, éventuellement aussi, sans doute-faux non gérée rejet d'erreur (ouvert le réel navigateur de la console pour voir):

const getValue1Async = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 500, "value1");
  });
};
const getValue2Async = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, "error");
  });
};

// This waits the full 500ms before failing, because it waits
// on p1, then on p2
(async () => {
  try {
    console.time("separate");
    const p1 = getValue1Async();
    const p2 = getValue2Async();
    const value1 = await p1;
    const value2 = await p2;
  } catch (e) {
    console.error(e);
  }
  console.timeEnd("separate");
})();

// This fails after just 100ms, because it doesn't wait for p1
// to finish first, it rejects as soon as p2 rejects
setTimeout(async () => {
  try {
    console.time("Promise.all");
    const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
  } catch (e) {
    console.timeEnd("Promise.all", e);
  }
}, 1000);
Open the real browser console to see the unhandled rejection error.

Et ici, nous rejeter à la fois p1 et p2, résultant en une non-faux non gérée rejet d'erreur sur p2:

const getValue1Async = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 500, "error1");
  });
};
const getValue2Async = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, "error2");
  });
};

// This waits the full 500ms before failing, because it waits
// on p1, then on p2
(async () => {
  try {
    console.time("separate");
    const p1 = getValue1Async();
    const p2 = getValue2Async();
    const value1 = await p1;
    const value2 = await p2;
  } catch (e) {
    console.error(e);
  }
  console.timeEnd("separate");
})();

// This fails after just 100ms, because it doesn't wait for p1
// to finish first, it rejects as soon as p2 rejects
setTimeout(async () => {
  try {
    console.time("Promise.all");
    const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
  } catch (e) {
    console.timeEnd("Promise.all", e);
  }
}, 1000);
Open the real browser console to see the unhandled rejection error.

Dans un commentaire vous avez demandé:

Côté question: est-ce la force en attente pour les deux (et de rejeter les résultats) await p1 && await p2?

Cela a le même questions autour de la promesse de rejet de votre code d'origine: Il faudra patienter jusqu' p1 résout même si p2 rejette plus tôt; il peut générer une sans doute-faux non gérée rejet d'erreur si p2 rejette avant d' p1 résout; et il génère un véritable non gérée rejet d'erreur si les deux p1 et p2 de rejet (parce qu' p2s'rejet n'est jamais manipulée).

Voici le cas où l' p1 résout et p2 dénonce:

const getValue1Async = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 500, false);
  });
};
const getValue2Async = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, "error");
  });
};

(async () => {
  try {
    const p1 = getValue1Async();
    const p2 = getValue2Async();
    console.log("waiting");
    await p1 && await p2;
  } catch (e) {
    console.error(e);
  }
  console.log("done waiting");
})();
Look in the real console (for the unhandled rejection error).

...et où les deux rejetons:

const getValue1Async = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 500, "error1");
  });
};
const getValue2Async = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, "error2");
  });
};

(async () => {
  try {
    const p1 = getValue1Async();
    const p2 = getValue2Async();
    console.log("waiting");
    await p1 && await p2;
  } catch (e) {
    console.error(e);
  }
  console.log("done waiting");
})();
Look in the real console (for the unhandled rejection error).

10voto

Kai Points 1123

Je pense que cela devrait fonctionner:

  const [value1, value2] = await Promise.all([getValue1Async(),getValue2Async()]);
 

Un exemple plus détaillé est ci-dessous au cas où cela aiderait à comprendre:

 const promise1 = async() => {
  return 3;
}

const promise2 = async() => {
  return 42;
}

const promise3 = async() => {
  return 500;
  // emulate an error
  // throw "something went wrong...";
}

const f1 = async() => {

  try {
    // returns an array of values
    const results = await Promise.all([promise1(), promise2(), promise3()]);
    console.log(results);
    console.log(results[0]);
    console.log(results[1]);
    console.log(results[2]);

    // assigns values to individual variables through 'array destructuring'
    const [value1, value2, value3] = await Promise.all([promise1(), promise2(), promise3()]);

    console.log(value1);
    console.log(value2);
    console.log(value3);

  } catch (err) {
    console.log("there was an error: " + err);
  }

}

f1(); 

1voto

user293023 Points 331

Utiliser .catch() et de la Promesse.tous les()

Assurez-vous de gérer les rejets correctement et vous pouvez les utiliser en toute sécurité des Promesses.tous les() sans être confrontés à des rejets non gérée. (Edit: clarification par la discussion: pas l'Erreur unhandled rejection mais simplement des rejets qui ne sont pas traitées par le code. Promise.all() va lancer la première promesse de rejet et d'ignorer le reste).

Dans l'exemple ci-dessous un tableau de [[erreur, les résultats], ...] est retournée de manière à faciliter le traitement des résultats et/ou des erreurs.

let myTimeout = (ms, is_ok) =>
  new Promise((resolve, reject) => 
    setTimeout(_=> is_ok ? 
                   resolve(`ok in ${ms}`) :
                   reject(`error in ${ms}`),
               ms));

let handleRejection = promise => promise
  .then((...r) => [null, ...r])
  .catch(e => [e]); 

(async _=> {
  let res = await Promise.all([
    myTimeout(100, true),
    myTimeout(200, false),
    myTimeout(300, true),
    myTimeout(400, false)
  ].map(handleRejection));
  console.log(res);
})();

Vous pouvez jeter dans un catch() pour arrêter d'attente pour tous (et ignorer les résultats de repos), cependant que vous ne pouvez le faire qu'une fois par blocs try/catch donc un drapeau has_thorwn doivent être entretenus et vérifiés pour s'assurer de l'absence d'erreurs non gérées arrive.

let myTimeout = (ms, is_ok) =>
  new Promise((resolve, reject) =>
    setTimeout(_=> is_ok ?
                   resolve(`ok in ${ms}`) :
                   reject(`error in ${ms}`),
               ms));

let has_thrown = false;

let handleRejection = promise => promise
  .then((...r) => [null, ...r])
  .catch(e => {
    if (has_thrown) {
      console.log('not throwing', e);
    } else {
      has_thrown = 1;
      throw e;
    }
  });

(async _=> {
  try {
    let res = await Promise.all([
      myTimeout(100, true),
      myTimeout(200, false),
      myTimeout(300, true),
      myTimeout(400, false)
    ].map(handleRejection));
    console.log(res);
  } catch(e) {
    console.log(e);
  }
  console.log('we are done');
})();

0voto

Qwerty Points 1165

Résout au lieu de Promesses

const wait = (ms, data) => new Promise( resolve => setTimeout(resolve, ms, data) )
const reject = (ms, data) => new Promise( (r, reject) => setTimeout(reject, ms, data) )
const e = e => 'err:' + e
const l = l => (console.log(l), l)

;(async function parallel() {

  let task1 = reject(500, 'parallelTask1').catch(e).then(l)
  let task2 = wait(2500, 'parallelTask2').catch(e).then(l)
  let task3 = reject(1500, 'parallelTask3').catch(e).then(l)

  console.log('WAITING')

  ;[task1, task2, task3] = [await task1, await task2,  await task3]

  console.log('FINISHED', task1, task2, task3)

})()

Comme il a été souligné dans d'autres réponses, le refus de la promesse peut soulever une exception non gérée.
Ce un .catch(e => e) est un joli petit truc qui attire l'erreur et le passe en bas de la chaîne, permettant la promesse d' resolve, au lieu de rejecting.

Si vous trouvez cette ES6 code laid voir plus convivial ici.

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