Aller au contenu principal

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, catch et finally ;
  • enchaîner plusieurs promesses à plat ;
  • éviter les deux erreurs classiques : oublier le catch et oublier le return.

Petite vérification​

Avant de commencer, vérifiez si vous savez...

  1. prévoir l'ordre d'affichage de console.log("A"); setTimeout(() => console.log("B"), 0); console.log("C");
  2. dire pourquoi une fonction qui appelle setTimeout ne peut pas simplement renvoyer son résultat avec return
  3. exécuter attendre.js et 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 :

ÉtatSignification
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​

Piège 1 : oublier le catch

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(...).

Piège 2 : oublier le return dans un then

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​


Quels sont les trois états possibles d'une promesse ?


Quelle fonction appelle-t-on dans new Promise pour signaler un échec ?


Quel bloc de la chaîne est exécuté dans tous les cas, succès comme échec ?


Que se passe-t-il si une promesse est rompue et qu'aucun catch ne l'attrape ?


Pourquoi faut-il écrire return chargerQuestion(...) dans un then qui enchaîne une autre promesse ?



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.


Bonne pratique - Une fonction qui renvoie une promesse

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.


Bonne pratique - Rejeter avec une Error

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.


Bonne pratique - Terminer par catch puis finally

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.


Bonne pratique - Un seul catch pour toute la chaîne

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​

NotionRésumé
PromesseObjet 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 / finallySuccès, échec, nettoyage.
ChaînageChaque then renvoie une promesse : on enchaîne à plat, avec un seul catch final.
PiègesToujours 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.