Une requête qui met trente secondes à répondre n'est pas une requête lente, c'est une requête perdue. Encore faut-il pouvoir décider soi-même du moment où l'on renonce.
Promise.race sert précisément à cela : mettre deux issues en concurrence et retenir celle qui se présente la première.
Définition
Promise.race(liste) renvoie une promesse qui prend l'issue de la première promesse réglée de la liste, qu'elle soit tenue ou rompue. Les autres continuent de tourner, leur résultat est ignoré.
const differe = (v, ms) => new Promise((r) => setTimeout(() => r(v), ms));
async function demo() {
const gagnant = await Promise.race([
differe("lent", 300),
differe("rapide", 50),
]);
console.log(gagnant); // rapide
}
demo();Le mot course est bien choisi : il n'y a qu'un vainqueur, et une promesse rompue arrivée avant les autres gagne au même titre qu'une promesse tenue.
Poser un délai maximum
C'est l'usage principal. On fait courir le travail réel contre une promesse qui échoue après un temps donné, et le premier des deux tranche.
const echoueApres = (ms) => new Promise((_, rejeter) => {
setTimeout(() => rejeter(new Error("Délai dépassé")), ms);
});
async function avecDelai(travail, ms) {
return Promise.race([travail, echoueApres(ms)]);
}
avecDelai(differe("réponse", 400), 100)
.then((v) => console.log(v))
.catch((err) => console.log(err.message)); // Délai dépasséL'appelant récupère une erreur claire au lieu d'une attente sans fin, et le catch traite le dépassement comme n'importe quel autre échec.
race, any : deux courses différentes
| Méthode | Ce qui déclenche le résultat | En cas d'échecs répétés |
|---|---|---|
Promise.race | La première réglée, succès ou échec | Le premier échec l'emporte |
Promise.any | La première tenue seulement | Un AggregateError une fois toutes rompues |
Ces trois colonnes distinguent deux besoins. race répond à la question du temps écoulé, any à celle du premier serveur qui répond correctement parmi plusieurs miroirs.
Promise.race([]) renvoie une promesse qui ne se règle jamais, puisqu'aucun concurrent ne peut gagner. Un await posé dessus fige la suite de la fonction sans lever la moindre erreur : vérifiez que la liste n'est pas vide avant de la passer.
Questions fréquentes
Le perdant est-il annulé ?
Non, et c'est le malentendu le plus coûteux. La promesse écartée poursuit son travail jusqu'au bout : la requête part quand même, le serveur répond quand même, la facture est la même. Pour interrompre réellement l'appel, il faut un AbortController.
Que devient le rejet du perdant ?
Il survient après coup, sans personne pour l'écouter, et le moteur le signale comme rejet non géré. Un catch vide branché sur la promesse écartée suffit à le neutraliser, ou mieux, une annulation propre qui l'empêche de se produire.
Comment réessayer plutôt que d'abandonner ?
En enveloppant l'appel dans une boucle qui relance après un échec, avec une pause croissante entre les tentatives. La course fixe la limite d'une tentative, la boucle décide du nombre d'essais : les deux mécanismes se combinent et n'ont pas à être confondus.