Chaînage de Promises en JavaScript
Découvrez le chaînage de promises en JavaScript pour séquencer des opérations asynchrones, gérer les erreurs et effectuer des nettoyages efficacement.
Le chaînage de promises vous permet d'exécuter des opérations asynchrones l'une après l'autre, où chaque étape ne commence qu'une fois la précédente terminée. Vous attachez une séquence de gestionnaires .then() à une promise, et chaque gestionnaire reçoit le résultat de l'étape précédente.
Avant les promises, séquencer des tâches asynchrones impliquait d'imbriquer des callbacks dans des callbacks — le tristement célèbre "callback hell" ou "pyramide de l'enfer" :
queryDatabase('users', (users) => {
queryDatabase('posts', (posts) => {
queryDatabase('comments', (comments) => {
// deeply nested, hard to read, error handling duplicated everywhere
});
});
});Le chaînage aplatit cette pyramide en une séquence lisible, du haut vers le bas, avec un seul endroit pour gérer les erreurs. Cette page explique comment le chaînage fonctionne réellement, le bogue le plus courant (un return manquant), la récupération d'erreurs et le nettoyage. Pour la syntaxe connexe qui s'appuie sur les promises, voir JavaScript : async/await.
Comment fonctionne le chaînage : chaque .then() renvoie une nouvelle Promise
C'est la mécanique fondamentale, tout le reste en découle. .then() ne renvoie pas la promise d'origine — il renvoie une toute nouvelle promise. Ce à quoi cette nouvelle promise se résout dépend de ce que votre gestionnaire renvoie :
- Renvoyer une valeur simple → le prochain
.then()reçoit cette valeur. - Renvoyer une promise → la chaîne attend qu'elle soit résolue, et le prochain
.then()reçoit sa valeur résolue (pas la promise elle-même). - Ne rien renvoyer → le prochain
.then()reçoitundefined. - Lever une erreur → la chaîne saute jusqu'au prochain
.catch().
Puisque chaque .then() renvoie une nouvelle promise, vous pouvez continuer à y attacher des appels .then() et transmettre une valeur tout au long :
Le cas puissant est le renvoi d'une promise depuis un gestionnaire. La chaîne se met en pause jusqu'à ce que cette promise soit résolue avant de continuer, ce qui est exactement la façon de séquencer des opérations asynchrones dépendantes :
Chaînage de Promises de base
Considérez le scénario où vous devez interroger une base de données, puis utiliser le résultat de cette requête pour en effectuer une autre. Chaque .then() renvoie la promise de la requête suivante, de sorte que la chaîne attend qu'une requête se termine avant de démarrer la suivante :
Le bogue n°1 : oublier return (la "chaîne détachée")
C'est l'erreur la plus courante dans le chaînage de promises. Si vous démarrez une opération asynchrone à l'intérieur d'un .then() mais que vous oubliez de renvoyer sa promise, la chaîne n'attend pas — le résultat est perdu et le prochain .then() s'exécute immédiatement avec undefined. La promise interne devient une chaîne "détachée" qui s'exécute de son côté.
Dans la version incorrecte ci-dessous, queryDatabase('posts') est appelée mais sa promise n'est pas renvoyée, donc le deuxième .then() affiche undefined au lieu des articles :
Ajouter return reconnecte la chaîne. Désormais, le deuxième .then() attend la requête des articles et reçoit son résultat :
Astuce : les fonctions fléchées avec un corps en expression renvoient automatiquement —
.then(r => queryDatabase(r))renvoie la promise, mais.then(r => { queryDatabase(r); })(avec des accolades) ne le fait pas.
Gestion des erreurs dans les chaînes
Un seul .catch() à la fin de la chaîne gère toute erreur levée — ou toute promise rejetée — à n'importe quelle étape précédente. Quand quelque chose échoue, la chaîne ignore tous les .then() restants et saute directement au prochain .catch().
Dans cet exemple, la première requête est rejetée, donc le .then() est entièrement ignoré et le contrôle arrive dans .catch() :
Pour un examen approfondi des schémas de rejet, voir Gestion des erreurs avec les Promises.
.catch() au milieu de la chaîne pour la récupération
Un .catch() n'a pas à être le dernier maillon. Placé au milieu d'une chaîne, il peut gérer une erreur, renvoyer une valeur de secours et laisser la chaîne continuer. C'est la différence entre récupérer d'un échec et abandonner toute la séquence.
Ci-dessous, la première étape échoue, mais un .catch() au milieu de la chaîne fournit une valeur par défaut et la chaîne continue :
Un .catch() au milieu de la chaîne récupère et reprend ; un .catch() terminal est le filet de sécurité final pour tout ce qui n'a pas été récupéré auparavant.
Nettoyage avec .finally()
.finally() s'exécute une fois que la promise est résolue — qu'elle ait été tenue ou rejetée. Il ne reçoit aucun argument et ne modifie pas la valeur qui transite dans la chaîne, ce qui en fait le choix idéal pour le nettoyage qui doit toujours avoir lieu : masquer un indicateur de chargement, fermer une connexion ou réactiver un bouton.
Exécuter des Promises en parallèle : Promise.all
Promise.all n'est pas du chaînage — le chaînage est séquentiel (l'un après l'autre), tandis que Promise.all exécute les promises en parallèle et attend qu'elles se terminent toutes. Utilisez-le quand les opérations ne dépendent pas les unes des autres, et qu'il n'y a donc aucune raison d'attendre l'une avant de démarrer la suivante.
Il prend un itérable de promises et renvoie une seule promise qui se résout en un array de leurs résultats, dans le même ordre que l'entrée. Toute valeur non-promise dans l'array (comme 42 ci-dessous) est automatiquement enveloppée dans une promise résolue. Si l'une quelconque des entrées est rejetée, l'ensemble de Promise.all est rejeté immédiatement avec cette erreur.
Pour Promise.all, Promise.race, Promise.allSettled et les autres combinateurs, voir l'API Promise.
Résumé
- Chaque
.then()renvoie une nouvelle promise ; la chaîne se lit de haut en bas plutôt qu'en imbrication. - Renvoyez des valeurs pour les transmettre, et renvoyez une promise depuis un gestionnaire pour que la chaîne l'attende.
- Le bogue le plus courant est un
returnmanquant à l'intérieur d'un.then()— cela détache le travail asynchrone interne et l'étape suivante reçoitundefined. - Un
.catch()terminal gère les erreurs de n'importe quelle étape précédente ; un.catch()au milieu de la chaîne peut récupérer et reprendre. - Utilisez
.finally()pour le nettoyage qui doit s'exécuter que la chaîne ait réussi ou échoué. - Utilisez
Promise.allpour les tâches indépendantes qui doivent s'exécuter en parallèle — c'est un outil différent du chaînage séquentiel. - Quand vous êtes à l'aise ici, async/await vous offre le même comportement avec une syntaxe à l'apparence synchrone.