Question 1Si delay(100, "A").then((v) => console.log(v)); est suivi de console.log("B");, dans quel ordre s'affiche la sortie ?
Promise — recevoir un résultat asynchrone avec then et catch
Reçois plus tard un résultat avec Promise : ses trois états, then / catch / finally, le chaînage et Promise.all.
Certaines opérations, comme envoyer une commande ou vérifier un stock, mettent du temps à renvoyer un résultat. Pour que la page ne se fige pas pendant ce temps, on écrit ce genre de code de façon à passer tout de suite à la ligne suivante et à récupérer le résultat plus tard. Cette façon de faire s'appelle la programmation asynchrone.
Cet article couvre Promise, qui représente un résultat disponible plus tard, et Promise.all, qui attend plusieurs Promises à la fois.
Recevoir un résultat plus tard — Promise et then
La réponse à une demande de places disponibles arrive 100 ms après l'envoi. setTimeout (une fonction qui appelle une fonction une seule fois, après le nombre de millisecondes indiqué) rend la main immédiatement, et l'exécution passe à la ligne suivante : tu ne peux donc pas renvoyer avec return une réponse qui arrive 100 ms plus tard.
À la place, tu renvoies une Promise (un objet qui représente un résultat disponible plus tard), et la réponse est transmise par son intermédiaire. La fonction que tu passes à new Promise((resolve) => { ... }) est appelée immédiatement, et appeler resolve(valeur) à l'intérieur fait passer la Promise de pending (en attente d'un résultat) à fulfilled (réussie). La fonction passée à la méthode then est appelée avec cette valeur une fois la Promise fulfilled.
// Renvoie une Promise fulfilled avec value au bout de ms millisecondes
function delay(ms, value) {
return new Promise((resolve) => {
setTimeout(() => resolve(value), ms);
});
}
// Lancer la vérification (encore pending à ce stade)
const seats = delay(100, "Il reste 12 places");
// Passer la fonction à appeler une fois la Promise fulfilled
seats.then((message) => {
console.log(message); // Il reste 12 places (affiché en 2e)
});
console.log("Vérification des places disponibles"); // Vérification des places disponibles (affiché en 1er)
then se contente de garder la fonction ; il ne l'appelle pas sur-le-champ. La fonction passée ne s'exécute qu'une fois les lignes suivantes terminées et la Promise fulfilled.Si tu écris le code qui utilise le résultat en dehors de then, il s'exécute avant que le résultat soit disponible, et la valeur n'est donc pas encore disponible. Garde les lignes qui utilisent le résultat dans la fonction passée à then, et place ce que tu veux faire pendant l'attente sur les lignes qui suivent, en dehors de then.
Ajoute une ligne d'attente à la fin du code
L'environnement d'exécution de cette page ne récupère que ce qui est affiché avant la fin de la dernière ligne. Dans un navigateur ou dans Node.js, la fonction de then s'exécute même si rien ne l'attend, mais ici, tu ajoutes await delay(1000); à la fin pour attendre une seconde. await (une syntaxe qui ne passe pas à la ligne suivante tant que le résultat d'une Promise n'est pas connu) est présenté dans le prochain article.
Gérer l'échec — reject / catch / finally
La vérification du code PIN d'une carte échoue quand le code est faux. Si tu n'as que resolve, un échec arrive comme une valeur ordinaire, exactement comme un succès, et la fonction qui la reçoit a besoin d'un if à chaque fois pour les distinguer.
La fonction passée à new Promise peut aussi recevoir reject comme deuxième paramètre. L'appeler, comme dans reject(new Error("message")), rend la Promise rejected (échouée). La fonction passée à catch reçoit cette Error quand la Promise est rejected, et la fonction passée à finally est appelée en dernier, en cas de succès comme d'échec.
function verifyPin(pin) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (pin === "4821") {
resolve("Code PIN vérifié");
} else {
// Signaler un échec en passant une Error à reject
reject(new Error("Code PIN incorrect"));
}
}, 100);
});
}
// then, catch et finally peuvent s'enchaîner les uns après les autres (la section suivante explique comment)
verifyPin("0000")
.then((message) => console.log(message)) // pas appelée, puisque la Promise est rejected
.catch((error) => console.log(error.message)) // Code PIN incorrect
.finally(() => console.log("Champ de saisie vidé"));
// Champ de saisie vidé (affiché en dernier, en cas de succès comme d'échec)
Quand une Promise est rejected, les fonctions then enchaînées avant catch sont sautées, et c'est la fonction du catch qui suit qui est appelée. Ce catch est une méthode de Promise, distincte de l'instruction try / catch. Si tu n'appelles jamais ni resolve ni reject, le code semble bloqué, sans affichage ni erreur.
Exécuter des étapes dans l'ordre — enchaîner then et return
Après avoir passé une commande, tu veux envoyer un e-mail de confirmation avec le numéro de commande renvoyé. Si tu écris la deuxième requête dans le premier then, puis la troisième à l'intérieur de celle-ci, l'indentation se creuse à chaque étape et le code devient difficile à lire.
then renvoie une nouvelle Promise : tu peux donc enchaîner les appels, comme dans .then(...).then(...). C'est ce qu'on appelle le chaînage (accrocher le then suivant à la Promise renvoyée par le then précédent). Une valeur que tu renvoies avec return depuis une fonction de then est reçue par la fonction du then suivant.
// delay est le même que dans le code de la première section
const placeOrder = () => delay(100, "A-2046");
const sendMail = (orderId) => delay(100, `E-mail de confirmation envoyé pour ${orderId}`);
placeOrder()
.then((orderId) => {
console.log(`Numéro de commande : ${orderId}`); // Numéro de commande : A-2046
return sendMail(orderId); // la chaîne attend que l'e-mail soit envoyé
})
.then((result) => {
console.log(result); // E-mail de confirmation envoyé pour A-2046
return "Toutes les étapes sont terminées"; // la valeur passe directement à la fonction suivante
})
.then((status) => console.log(status)); // Toutes les étapes sont terminées
Sans return, la fonction renvoie undefined : le then suivant s'exécute donc sans attendre l'envoi de l'e-mail, et result vaut undefined. Aucune erreur n'apparaît ; le résultat de l'envoi est simplement perdu, ce qui rend cet oubli facile à rater.
Dès que tu ajoutes des accolades, tu ne peux plus omettre return
Une fonction fléchée sans accolades, comme .then((id) => sendMail(id)), renvoie telle quelle la Promise de sendMail : le then suivant attend donc. Si tu entoures le corps d'accolades pour ajouter une ligne de journal au milieu, remets le return, comme dans return sendMail(id);.
Attendre plusieurs résultats à la fois — Promise.all
À l'ouverture du tableau de bord d'administration, tu demandes séparément le chiffre d'affaires du jour, le nombre de commandes et le nombre de demandes de contact, et tu n'affiches le panneau récapitulatif qu'une fois les trois résultats arrivés. Si tu accroches un then distinct à chacune des trois Promises, celle qui arrive en dernier change à chaque fois : il n'y a donc aucun endroit unique où vérifier que tout est arrivé.
Passe à Promise.all (une méthode qui prend un tableau de Promises et attend que tous leurs résultats soient arrivés) les Promises renvoyées par les trois fonctions : tu reçois les trois résultats d'un coup, sous forme de tableau. Si une seule d'entre elles est rejected, c'est catch qui prend le relais au lieu de then.
// delay est le même que dans le code de la première section
const loadSales = () => delay(100, 184000);
const loadOrders = () => delay(50, 42);
const loadInquiries = () => delay(80, 3);
// Les trois démarrent dès l'appel des fonctions. Promise.all attend qu'elles soient toutes arrivées
Promise.all([loadSales(), loadOrders(), loadInquiries()])
// Recevoir le tableau de résultats en le déstructurant par position
.then(([sales, orders, inquiries]) => {
// Reçus dans l'ordre écrit dans le tableau, pas dans l'ordre où ils se terminent
console.log(`Ventes : ${sales} yens`); // Ventes : 184000 yens
console.log(`Commandes : ${orders}`); // Commandes : 42
console.log(`Demandes : ${inquiries}`); // Demandes : 3
})
.catch((error) => console.log(error.message));
Promise.all ne lance lui-même aucune requête ; il se contente d'attendre que toutes soient arrivées. Le tableau ci-dessous montre, pour chaque état des Promises passées, la Promise renvoyée par Promise.all et la fonction qui est appelée.
| État des Promises passées | Promise renvoyée par Promise.all | Fonction appelée et valeur reçue |
|---|---|---|
| Toutes fulfilled | Fulfilled dès que la dernière est fulfilled | then reçoit un tableau dans l'ordre de passage |
| Une est rejected | Rejected dès que celle-ci est rejected | catch reçoit cette Error |
| Deux ou plus sont rejected | Rejected dès que la première est rejected | catch ne reçoit que la première Error |
| Les autres sont fulfilled, mais une reste pending | Reste pending | Ni then ni catch n'est appelé |
Vérification des connaissances
Répondez à chaque question une par une.
Question 2Si tu enchaînes .then(A).catch(B).finally(C) sur une Promise qui est rejected, lesquelles sont appelées ?
Question 3Quel tableau reçoit le then de Promise.all([delay(100, "A"), delay(50, "B")]) ?