Les promesses
Un ticket de retrait pour un résultat qui n'est pas encore prêt : plus de pyramide, et les erreurs au même endroit.
Objectifs de la séance
- comprendre les trois états d'une promesse : en attente, tenue, rompue ;
- créer une promesse avec
new Promise; - la consommer avec
then,catchetfinally; - enchaîner plusieurs promesses à plat ;
- éviter les deux erreurs classiques : oublier le
catchet oublier lereturn.
Petite vérification
Avant de commencer, vérifiez si vous savez...
- prévoir l'ordre d'affichage de
console.log("A"); setTimeout(() => console.log("B"), 0); console.log("C"); - dire pourquoi une fonction qui appelle
setTimeoutne peut pas simplement renvoyer son résultat avecreturn - exécuter
attendre.jset lire la dernière ligne affichée
📌 Réponse attendue
Notions théoriques
Une promesse, c'est un ticket de retrait
Vous déposez un colis en consigne : on vous remet un ticket. Le colis n'est pas encore prêt, mais le ticket garantit qu'on vous préviendra : soit le colis sera prêt, soit il y aura un problème.
Une promesse (Promise) est ce ticket : un objet qui représente un résultat futur. Elle a toujours l'un de ces trois états :
| État | Signification |
|---|---|
| pending (en attente) | Le résultat n'est pas encore là. |
| fulfilled (tenue) | Succès : le résultat est disponible. |
| rejected (rompue) | Échec : une erreur s'est produite. |
Une promesse change d'état une seule fois : de « en attente » vers « tenue » ou « rompue ». Ensuite, elle ne bouge plus.
Créer une promesse
On crée une promesse avec new Promise, en lui donnant une fonction qui reçoit deux outils :
resolve(valeur): appelle-la pour dire « succès, voici le résultat » ;reject(erreur): appelle-la pour dire « échec, voici l'erreur ».
Voici la promesse la plus utile pour s'entraîner : une pause.
function attendre(millisecondes) {
return new Promise((resolve) => {
setTimeout(resolve, millisecondes);
});
}
Et une promesse qui peut réussir ou échouer, comme une vraie requête vers un serveur :
function chargerQuestion(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id < 1 || id > 3) {
reject(new Error(`Question ${id} introuvable`));
return;
}
resolve({ id, enonce: `Énoncé de la question ${id}` });
}, 300);
});
}
Utiliser une promesse : then, catch, finally
chargerQuestion(1)
.then((question) => {
console.log("Reçu :", question.enonce); // appelé en cas de succès
})
.catch((erreur) => {
console.log("Erreur :", erreur.message); // appelé en cas d'échec
})
.finally(() => {
console.log("Chargement terminé"); // appelé dans tous les cas
});
Reçu : Énoncé de la question 1
Chargement terminé
Avec chargerQuestion(99), c'est le catch qui s'exécute :
Erreur : Question 99 introuvable
Chargement terminé
Cela ressemble beaucoup à try / catch / finally de la séance sur les erreurs : l'idée est la même, mais pour du code qui se termine plus tard.
Enchaîner à plat
C'est la grande victoire sur la pyramide : chaque .then renvoie une nouvelle promesse, donc on enchaîne les étapes les unes sous les autres.
chargerQuestion(1)
.then((question) => {
console.log("Première :", question.enonce);
return chargerQuestion(question.id + 1); // on RENVOIE la promesse suivante
})
.then((suivante) => {
console.log("Seconde :", suivante.enonce);
})
.catch((erreur) => {
console.log("Erreur :", erreur.message); // un seul catch pour toute la chaîne
});
Un seul .catch à la fin attrape l'échec de n'importe quelle étape.
Les deux pièges classiques
Une promesse rompue sans catch provoque une erreur « unhandled rejection » : dans Node.js, le programme s'arrête. Dans une application,
l'échec passe inaperçu et l'écran reste bloqué. Toute chaîne de promesses doit se terminer par un .catch(...).
Si vous écrivez .then((q) => { chargerQuestion(q.id + 1); }) sans return, l'étape suivante ne patiente pas : elle reçoit tout de suite undefined,
et l'erreur éventuelle de la promesse oubliée n'est jamais attrapée. Écrivez toujours return.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Votre mission : créer puis enchaîner des promesses qui simulent des requêtes. Travaillez dans le fichier labo-js/promesses.js.
Étape 1 — Créer la promesse « pause »
Complétez attendre(millisecondes), qui renvoie une promesse tenue après le délai.
Une fonction asynchrone renvoie toujours sa promesse (return new Promise(...)). Celui qui l'appelle peut alors choisir quoi faire à l'arrivée du résultat.
Étape 2 — Simuler une requête qui peut échouer
Copiez chargerQuestion dans votre fichier. Écrivez ensuite la condition qui rompt la promesse pour un id hors de la plage 1 à 3.
Rompez toujours une promesse avec un objet Error (pas avec un simple texte) : il contient un message et une trace de l'endroit où le problème est apparu.
Étape 3 — Consommer la promesse
Affichez l'énoncé de la question 1, gérez l'échec et signalez la fin du chargement.
Dans cet ordre : .then (succès), .catch (échec), .finally (nettoyage). C'est la structure que vous retrouverez dans chaque chargement de données.
Étape 4 — Enchaîner deux chargements
Après la question 1, chargez automatiquement la question suivante.
Si la question 4 n'existe pas, l'échec saute directement au .catch final. Essayez avec chargerQuestion(3) comme point de départ pour le constater.
📌 Une solution
Ce qu'il faut retenir
| Notion | Résumé |
|---|---|
| Promesse | Objet qui représente un résultat futur : en attente, tenue ou rompue (un seul changement d'état). |
new Promise((resolve, reject) => ...) | resolve(valeur) pour le succès, reject(erreur) pour l'échec. |
then / catch / finally | Succès, échec, nettoyage. |
| Chaînage | Chaque then renvoie une promesse : on enchaîne à plat, avec un seul catch final. |
| Pièges | Toujours un catch (sinon unhandled rejection), et toujours un return dans un then qui enchaîne. |
Aperçu de la prochaine séance
Les promesses règlent la pyramide, mais les .then à répétition restent un peu lourds. Avec async et await, vous allez écrire du code asynchrone
qui se lit comme du code normal, de haut en bas, avec les bons vieux try / catch.