À propos de ce cours
Chapitre 18 — La programmation asynchrone · Leçon 4 : Les promesses (Promise, then, catch, Promise.all)
• expliquer ce qu'est une promesse et ses trois états ;
• créer une promesse avec
new Promise et transformer une fonction à callback en promesse ;• enchaîner des traitements avec
then, catch et finally; • lancer des traitements en parallèle avec
Promise.all, allSettled, race et any. 1. Qu'est-ce qu'une promesse ?
Une promesse (Promise) est un objet qui représente le résultat futur d'une opération asynchrone.
Comme un ticket de retrait au pressing : vous l'obtenez tout de suite, et il vous permettra plus tard de récupérer
votre vêtement… ou d'apprendre qu'il y a eu un problème.
Au lieu de donner un callback à la fonction (inversion de contrôle), c'est la fonction qui nous rend une promesse : nous gardons le contrôle de la suite.

Figure 4 — Les états d'une promesse et les méthodes qui y réagissent.
2. Créer et consommer une promesse
Le constructeur new Promise((resolve, reject) => { … }) reçoit une fonction exécutée immédiatement.
On appelle resolve(valeur) en cas de succès, ou reject(erreur) en cas d'échec.
const promesse = new Promise((resolve, reject) => {
const reussite = true; // essayez avec false
setTimeout(() => {
if (reussite) {
resolve("Données reçues");
} else {
reject(new Error("Échec du chargement"));
}
}, 1000);
});
console.log("État juste après la création :", promesse);
promesse
.then((valeur) => console.log("Succès :", valeur))
.catch((erreur) => console.log("Erreur :", erreur.message))
.finally(() => console.log("Terminé (succès ou échec)"));
Résultat dans la console
État juste après la création : Promise { <pending> }
Succès : Données reçues
Terminé (succès ou échec)
.then(f):freçoit la valeur quand la promesse est tenue ;.catch(f):freçoit l'erreur quand elle est rejetée ;.finally(f):fs'exécute dans les deux cas (par exemple pour masquer un indicateur de chargement).
3. « Promisifier » une fonction à callback
On enveloppe l'ancienne fonction dans une promesse. Exemple classique : une version de setTimeout qui renvoie une promesse.
// Transformer setTimeout en promesse : « promisifier »
function attendre(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
console.log("Départ");
attendre(1000)
.then(() => {
console.log("1 seconde plus tard");
return attendre(500); // on renvoie une promesse : le then suivant l'attend
})
.then(() => {
console.log("1,5 seconde plus tard");
return 42; // on renvoie une valeur simple
})
.then((nombre) => console.log("Valeur reçue :", nombre));
Résultat dans la console
Départ 1 seconde plus tard 1,5 seconde plus tard Valeur reçue : 42
La règle du chaînage : chaque
.then() renvoie une nouvelle promesse.
Si la fonction du then renvoie une valeur, le then suivant la reçoit. Si elle renvoie une promesse,
le then suivant attend qu'elle soit réglée. Si elle lève une erreur, on saute jusqu'au prochain catch. 4. Fini le callback hell : le chaînage
Le même scénario qu'à la leçon 3 devient une chaîne plate, avec un seul catch pour toutes les étapes :
// Les fonctions de la leçon 3, réécrites pour RENVOYER une promesse
function chargerUtilisateur(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id <= 0) reject(new Error("Identifiant invalide"));
else resolve({ id, nom: "Amina" });
}, 300);
});
}
function chargerCommandes(utilisateur) {
return new Promise((resolve) =>
setTimeout(() => resolve([{ ref: "C-101" }, { ref: "C-102" }]), 300));
}
function chargerDetails(commande) {
return new Promise((resolve) =>
setTimeout(() => resolve({ ref: commande.ref, articles: [12.5, 7, 30] }), 300));
}
function calculerTotal(details) {
return new Promise((resolve) =>
setTimeout(() => resolve(details.articles.reduce((s, p) => s + p, 0)), 300));
}
// Une chaîne plate, et UN SEUL catch pour toutes les étapes
chargerUtilisateur(1)
.then((utilisateur) => chargerCommandes(utilisateur))
.then((commandes) => chargerDetails(commandes[0]))
.then((details) => calculerTotal(details))
.then((total) => console.log("Total :", total, "DT"))
.catch((err) => console.log("Erreur :", err.message));
// Avec un identifiant invalide, on saute directement au catch
chargerUtilisateur(-1)
.then((utilisateur) => chargerCommandes(utilisateur))
.then(() => console.log("Cette ligne ne s'affiche jamais"))
.catch((err) => console.log("Erreur :", err.message));
Résultat dans la console
Erreur : Identifiant invalide Total : 49.5 DT
L'erreur s'affiche en premier : la seconde chaîne échoue dès la première étape (300 ms), alors que la première chaîne a besoin de quatre étapes (1 200 ms).
Oublier le
return dans un then casse la chaîne : le then suivant reçoit undefined
et n'attend rien. Avec une fonction fléchée sans accolades (x => f(x)), le return est implicite.5. Plusieurs promesses à la fois
Quand des opérations sont indépendantes, il est inutile de les attendre l'une après l'autre : on les lance ensemble.
function charger(nom, ms, echoue = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (echoue) reject(new Error(`${nom} indisponible`));
else resolve(`${nom} OK`);
}, ms);
});
}
const debut = Date.now();
const duree = () => `${Math.round((Date.now() - debut) / 100) / 10} s`;
Promise.all([
charger("Météo", 1000),
charger("Actualités", 800),
charger("Agenda", 1200),
])
.then((resultats) => {
console.log("Promise.all :", resultats, "en", duree());
})
.catch((err) => console.log("Promise.all a échoué :", err.message));
Résultat dans la console
Promise.all : [ 'Météo OK', 'Actualités OK', 'Agenda OK' ] en 1.2 s
Durée totale ≈ celle de la plus longue (1,2 s), et non la somme (3 s). Les résultats arrivent dans l'ordre du tableau, pas dans l'ordre de fin.
function charger(nom, ms, echoue = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (echoue) reject(new Error(`${nom} indisponible`));
else resolve(`${nom} OK`);
}, ms);
});
}
// all : échoue dès qu'UNE promesse échoue
Promise.all([charger("A", 300), charger("B", 100, true), charger("C", 200)])
.then((r) => console.log("all :", r))
.catch((e) => console.log("all → erreur :", e.message));
// allSettled : attend TOUT et donne le bilan de chacune
Promise.allSettled([charger("A", 300), charger("B", 100, true)])
.then((bilans) =>
bilans.forEach((b) =>
console.log("allSettled :", b.status, b.value ?? b.reason.message)));
// race : la PREMIÈRE réglée gagne (succès ou échec)
Promise.race([charger("Lent", 500), charger("Rapide", 150)])
.then((r) => console.log("race :", r));
// any : la première RÉUSSIE gagne (ignore les échecs)
Promise.any([charger("X", 100, true), charger("Y", 400)])
.then((r) => console.log("any :", r));
Résultat dans la console
all → erreur : B indisponible race : Rapide OK allSettled : fulfilled A OK allSettled : rejected B indisponible any : Y OK
| Méthode | Réussit quand… | Échoue quand… | Usage typique |
|---|---|---|---|
Promise.all | toutes réussissent (tableau des valeurs) | une seule échoue (dès le premier échec) | Charger tout ce qui est indispensable à une page |
Promise.allSettled | toujours, quand toutes sont réglées (bilan de chacune) | jamais | Envoyer 10 e-mails et savoir lesquels sont partis |
Promise.race | la première réglée est un succès | la première réglée est un échec | Imposer un délai maximal (timeout) |
Promise.any | une au moins réussit (la première) | toutes échouent | Interroger plusieurs serveurs miroirs |
6. Un cas réel : fetch
fetch renvoie une promesse. À tester dans la console du navigateur :
// À tester dans le navigateur (console F12) : fetch renvoie une promesse
fetch("https://jsonplaceholder.typicode.com/users/1")
.then((reponse) => {
if (!reponse.ok) {
throw new Error(`Erreur HTTP ${reponse.status}`); // fetch ne rejette PAS sur 404
}
return reponse.json(); // .json() renvoie aussi une promesse
})
.then((utilisateur) => console.log(utilisateur.name, "-", utilisateur.email))
.catch((err) => console.log("Problème :", err.message));
fetch ne rejette sa promesse qu'en cas de problème réseau. Une réponse 404 ou 500 est un « succès » technique : il faut tester reponse.ok soi-même.• Une promesse représente un résultat futur : pending, puis fulfilled (valeur) ou rejected (erreur), une seule fois.
•
new Promise((resolve, reject) => …) crée une promesse ; then, catch, finally la consomment.• Chaque
then renvoie une nouvelle promesse : on enchaîne à plat, avec un seul catch à la fin.•
Promise.all lance en parallèle et échoue au premier échec ; allSettled donne un bilan complet ; race et any gardent la première.🛠 Activité 4 — Passer de la bibliothèque à callbacks à la bibliothèque à promesses
Mission : Reprenez votre fichier de l'activité 3 et réécrivez-le entièrement avec des promesses.
Questions pour avancer (répondez dans votre compte rendu, puis appliquez dans votre projet) :
- Réécrivez
rechercherLivre,verifierDisponibiliteetreserverpour qu'elles renvoient une promesse. Que devient le paramètrecallback? Que remplacecallback(err)? - Enchaînez les trois appels avec
then. Combien de niveaux d'indentation obtenez-vous maintenant ? Où placez-vous la gestion d'erreur ? - Si le livre n'est pas disponible, la réservation doit être refusée. Faut-il faire
throw new Error(…)oureturn Promise.reject(…)dans lethen? Testez les deux : y a-t-il une différence de résultat ? - Retirez un
returndans un desthen. Que reçoit lethensuivant ? L'ordre d'affichage change-t-il ? - Un utilisateur veut réserver trois livres d'un coup. Utilisez
Promise.all. Que se passe-t-il si l'un des trois est indisponible ? Est-ce le comportement souhaité ? Quelle méthode choisiriez-vous pour lui afficher « 2 réservés, 1 indisponible » ? - Écrivez
avecDelaiMax(promesse, ms)qui rejette avec « Trop long » si la promesse n'est pas réglée enmsmillisecondes. Quelle méthode statique dePromiseutilisez-vous ? - Dans la console du navigateur, utilisez
fetchsurhttps://jsonplaceholder.typicode.com/todos/1puis sur…/todos/999999. Lecatchest-il appelé pour la seconde URL ? Pourquoi ?
Je vérifie / je rends :
- ☐ Le fichier
bibliotheque-promesses.js. - ☐ La fonction
avecDelaiMaxtestée avec un cas qui réussit et un cas qui dépasse le délai.
← 18.3 — Les callbacks asynchrones et le « callback hell »18.5 — async et await →