Aller au contenu principal

Promise combinators

· 5 minutes de lecture
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Depuis l'introduction des promesses dans ES2015, JavaScript a pris en charge exactement deux combinateurs de promesses : les méthodes statiques Promise.all et Promise.race.

Deux nouvelles propositions sont actuellement en cours de standardisation : Promise.allSettled et Promise.any. Avec ces ajouts, il y aura un total de quatre combinateurs de promesses en JavaScript, chacun permettant différents cas d'utilisation.

Voici un aperçu des quatre combinateurs :

nomdescriptionstatut
Promise.allSettledne s'arrête pas brusquementajouté dans ES2020 ✅
Promise.alls'arrête lorsque l'une des valeurs d'entrée est rejetéeajouté dans ES2015 ✅
Promise.races'arrête lorsque l'une des valeurs d'entrée est achevéeajouté dans ES2015 ✅
Promise.anys'arrête lorsque l'une des valeurs d'entrée est réaliséeajouté dans ES2021 ✅

Jetons un œil à un exemple d'utilisation pour chaque combinateur.

Promise.all

Promise.all vous permet de savoir si toutes les promesses d'entrée ont été réalisées ou si l'une d'entre elles a été rejetée.

Imaginez que l'utilisateur clique sur un bouton et que vous souhaitez charger des feuilles de style pour rendre une interface utilisateur complètement nouvelle. Ce programme lance une requête HTTP pour chaque feuille de style en parallèle :

const promises = [
fetch('/component-a.css'),
fetch('/component-b.css'),
fetch('/component-c.css'),
];
try {
const styleResponses = await Promise.all(promises);
enableStyles(styleResponses);
renderNewUi();
} catch (reason) {
displayError(reason);
}

Vous voulez uniquement commencer à rendre la nouvelle interface utilisateur une fois que toutes les requêtes ont réussi. Si quelque chose tourne mal, vous voulez afficher un message d'erreur dès que possible, sans attendre que les autres tâches soient terminées.

Dans ce cas, vous pouvez utiliser Promise.all: vous voulez savoir quand toutes les promesses sont réalisées, ou dès que l'une d'entre elles est rejetée.

Promise.race

Promise.race est utile si vous voulez exécuter plusieurs promesses, et soit…

  1. faire quelque chose avec le premier résultat réussi qui arrive (dans le cas où l'une des promesses est réalisée), ou
  2. faire quelque chose dès qu'une des promesses est rejetée.

Autrement dit, si l'une des promesses est rejetée, vous voulez conserver ce rejet pour traiter le cas d'erreur séparément. L'exemple suivant fait exactement cela :

try {
const result = await Promise.race([
performHeavyComputation(),
rejectAfterTimeout(2000),
]);
renderResult(result);
} catch (error) {
renderError(error);
}

Nous lançons une tâche computationnelle coûteuse qui pourrait prendre longtemps, mais nous la mettons en concurrence avec une promesse qui est rejetée après 2 secondes. En fonction de la première promesse réalisée ou rejetée, nous rendons soit le résultat calculé, soit le message d'erreur, dans deux chemins de code distincts.

Promise.allSettled

Promise.allSettled vous donne un signal lorsque toutes les promesses d'entrée sont achevées, ce qui signifie qu'elles sont soit réalisées soit rejetées. Ceci est utile dans les cas où vous ne vous souciez pas de l'état de la promesse, vous voulez juste savoir quand le travail est terminé, que ce soit réussi ou non.

Par exemple, vous pouvez lancer une série d'appels API indépendants et utiliser Promise.allSettled pour vous assurer qu'ils sont tous terminés avant de faire autre chose, comme retirer un indicateur de chargement :

const promises = [
fetch('/api-call-1'),
fetch('/api-call-2'),
fetch('/api-call-3'),
];
// Imaginez que certaines de ces requêtes échouent et que d'autres réussissent.

await Promise.allSettled(promises);
// Tous les appels API sont terminés (échoués ou réussis).
removeLoadingIndicator();

Promise.any

Promise.any vous donne un signal dès que l'une des promesses est remplie. C'est similaire à Promise.race, sauf que any ne rejette pas immédiatement lorsqu'une des promesses est rejetée.

const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
// Une des promesses a été remplie.
console.log(first);
// → par exemple 'b'
} catch (error) {
// Toutes les promesses ont été rejetées.
console.assert(error instanceof AggregateError);
// Journalisez les valeurs de rejet :
console.log(error.errors);
// → [
// <TypeError: Échec de la récupération de /endpoint-a>,
// <TypeError: Échec de la récupération de /endpoint-b>,
// <TypeError: Échec de la récupération de /endpoint-c>
// ]
}

Cet exemple de code vérifie quel endpoint répond le plus rapidement, puis l'enregistre. Ce n'est que si toutes les requêtes échouent que nous arrivons dans le bloc catch, où nous pouvons ensuite gérer les erreurs.

Les rejets de Promise.any peuvent représenter plusieurs erreurs à la fois. Pour prendre en charge cela au niveau du langage, un nouveau type d'erreur appelé AggregateError est introduit. En plus de son utilisation de base dans l'exemple ci-dessus, les objets AggregateError peuvent également être créés de manière programmatique, tout comme les autres types d'erreurs :

const aggregateError = new AggregateError([errorA, errorB, errorC], 'Quelque chose s'est mal passé !');