Une promesse ne rend pas sa valeur, elle la remet. Il faut donc lui indiquer d'avance à qui, et then est le guichet prévu pour cela.
Sa vraie force n'est pas de recevoir la valeur, mais de renvoyer à son tour une promesse. C'est ce détail qui transforme une suite d'étapes en une colonne lisible.
Définition
then(traitement) enregistre une fonction à exécuter lorsque la Promise est tenue, et renvoie une nouvelle promesse portant le résultat de ce traitement. Chaque maillon nourrit le suivant.
Promise.resolve(2)
.then((v) => v * 3) // 6
.then((v) => Promise.resolve(v + 1)) // 7, la promesse est déballée
.then((v) => console.log("valeur", v)); // valeur 7Le deuxième maillon rend une promesse et non un nombre : la chaîne l'attend et transmet sa valeur, sans jamais produire une promesse de promesse. Un return de valeur simple et un return de promesse se comportent donc pareil.
Ce qui se passe quand ça casse
Un throw dans un then saute tous les maillons suivants et va directement au premier catch rencontré. Après quoi la chaîne repart normalement.
Promise.resolve("dossier")
.then(() => { throw new Error("coupure réseau"); })
.then(() => console.log("jamais exécuté"))
.catch((err) => console.log("attrapé :", err.message))
.then(() => console.log("la chaîne repart"));
// attrapé : coupure réseau
// la chaîne repartUn seul catch en fin de chaîne couvre donc toutes les étapes, alors qu'un style à callback aurait demandé un test d'erreur à chaque niveau.
Le second argument, et pourquoi on l'évite
then accepte une deuxième fonction, appelée en cas de rejet. Elle ressemble à catch, sans en avoir la portée.
| Écriture | Ce qu'elle couvre |
|---|---|
then(ok, echec) | Le rejet arrivé avant, jamais l'erreur lancée par ok |
then(ok).catch(echec) | Le rejet arrivé avant et l'erreur lancée par ok |
Ces deux colonnes expliquent la préférence générale pour catch : le second argument laisse passer l'erreur du traitement qu'il accompagne, ce qui produit un rejet non géré difficile à retrouver.
then ne s'exécute jamais dans le tour courant, même sur une promesse déjà tenue. Le traitement part dans la file des microtâches et attend la fin du code en cours, ce qui garantit un ordre stable quelle que soit la vitesse de l'opération.
Questions fréquentes
Faut-il préférer then ou await ?
await l'emporte dès qu'il y a plusieurs étapes dépendantes ou une logique conditionnelle, parce que le code redevient linéaire. then reste plus court sur une transformation unique, et il est le seul disponible en dehors d'une fonction async dans les environnements anciens.
Pourquoi mon then reçoit-il undefined ?
Parce que le maillon précédent n'a rien renvoyé. Une fonction fléchée à accolades a besoin d'un return explicite : (v) => { v * 2 } ne rend rien, contrairement à (v) => v * 2. C'est l'oubli le plus courant dans une chaîne.
Peut-on brancher plusieurs then sur la même promesse ?
Oui, et ce n'est pas la même chose qu'un chaînage. Deux then posés sur la même variable reçoivent tous deux la valeur d'origine et créent deux branches indépendantes, alors qu'un chaînage fait passer la valeur de maillon en maillon.