frontend / javascript
Promises
Explication
Ce que vous allez apprendre
- Créer une Promise et décrire ses trois états possibles
- Chaîner des opérations asynchrones avec
.then()et.catch()sans les imbriquer - Repérer et corriger l'oubli de
returndans une chaîne de.then() - Choisir entre
Promise.alletPromise.allSettledselon la tolérance à l'échec partiel - Relier ce mécanisme à
async/await, vu à la leçon suivante
Dans quel contexte ?
Une fonction chargerProfilComplet(id) enchaîne trois appels réseau : récupérer l'utilisateur, puis ses préférences, puis ses statistiques. Après une modification, le développeur remarque que la fonction retourne systématiquement undefined alors que les requêtes réussissent bien côté serveur (visible dans l'onglet Réseau des DevTools). En relisant le code, il découvre qu'un .then(data => { traiterDonnees(data); }) a été ajouté sans return : la Promise suivante dans la chaîne ne reçoit donc jamais le résultat attendu, juste undefined.
1. Le problème que les Promises résolvent
La leçon précédente a mis en évidence le principal défaut des callbacks : leur imbrication devient vite illisible dès qu'on enchaîne plusieurs opérations asynchrones. Les Promises transforment une opération future en un objet à part entière, qu'on peut manipuler comme n'importe quelle autre valeur.
2. Trois états, une seule transition possible
Une Promise traverse toujours l'un de trois états : d'abord "en attente" (pending), puis, une fois l'opération terminée, soit "tenue" (fulfilled, un succès) soit "rejetée" (rejected, un échec). Cette transition est définitive et irréversible.
| État | Signification | Peut-il changer ensuite ? |
|---|---|---|
pending | Opération encore en cours | Oui, vers fulfilled ou rejected |
fulfilled | Opération réussie, valeur disponible | Non, état final |
rejected | Opération échouée, erreur disponible | Non, état final |
Prérequis
Cette leçon prolonge directement la précédente sur les callbacks : gardez en tête le problème de la pyramide de callbacks, les Promises existent spécifiquement pour le résoudre.
3. Réagir au résultat : then et catch
Le .then() réagit au succès, le .catch() à l'échec. Chacune de ces méthodes retourne elle-même une nouvelle Promise, ce qui permet de chaîner les étapes de façon linéaire, au lieu de les imbriquer comme avec des callbacks.
4. Le piège le plus fréquent chez les débutants
Un oubli très courant : ne pas mettre de return à l'intérieur d'un .then(). Sans lui, la Promise suivante dans la chaîne ne sait pas qu'elle doit attendre le résultat de l'étape précédente, et l'exécution continue en avance, avec des données incomplètes.
Piège fréquent
fetch(url).then(res => { res.json(); }).then(data => console.log(data)) affiche undefined : le return manque avant res.json(). La version correcte est .then(res => { return res.json(); }) ou, plus court, .then(res => res.json()).
5. Attendre plusieurs opérations à la fois
Au-delà du chaînage simple, Promise.all attend plusieurs opérations en parallèle, avec un échec global si une seule échoue. Promise.allSettled attend tout sans jamais rejeter, utile pour ne perdre aucun résultat même partiel.
Ces outils préparent directement la leçon suivante : async/await, qui n'est rien d'autre qu'une façon plus agréable d'écrire exactement ce même mécanisme de Promises.
Commandes & code
Promises
Représenter une valeur future, avec chaînage et gestion d'erreurs unifiée.
// Une Promise represente une operation asynchrone dans un de 3 etats :
// pending -> fulfilled (resolve) ou rejected (reject)
function chargerDonnees(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id <= 0) {
reject(new Error("ID invalide"));
} else {
resolve({ id, nom: `Element ${id}` });
}
}, 500);
});
}
chargerDonnees(1)
.then((donnees) => console.log("Recu :", donnees))
.catch((erreur) => console.error("Erreur :", erreur.message))
.finally(() => console.log("Operation terminee (succes ou echec)"));
// --- Chainage : resoudre le callback hell ---
chargerDonnees(1)
.then((res1) => chargerDonnees(res1.id + 1)) // retourner une Promise chaine automatiquement
.then((res2) => chargerDonnees(res2.id + 1))
.then((res3) => console.log("Chaine complete :", res3))
.catch((erreur) => console.error("Une etape a echoue :", erreur.message));
// Un SEUL .catch() capture l'erreur de N'IMPORTE QUELLE etape precedente
// --- Promise.all : attendre PLUSIEURS promises, echoue si UNE seule echoue ---
Promise.all([chargerDonnees(1), chargerDonnees(2), chargerDonnees(3)])
.then((resultats) => console.log("Tous termines :", resultats))
.catch((erreur) => console.error("Au moins un a echoue :", erreur.message));
// --- Promise.allSettled : attend toutes, ne rejette JAMAIS, donne le detail de chacune ---
Promise.allSettled([chargerDonnees(1), chargerDonnees(-1), chargerDonnees(3)])
.then((resultats) => {
resultats.forEach((r) => {
if (r.status === "fulfilled") console.log("OK :", r.value);
else console.log("Echec :", r.reason.message);
});
});
// --- Promise.race : se resout/rejette des que LA PREMIERE se termine ---
Promise.race([
chargerDonnees(1),
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 300)),
]).then(console.log).catch((e) => console.error("Race perdue :", e.message));
// --- Promise.any : se resout des que LA PREMIERE reussit, ignore les echecs ---
Promise.any([chargerDonnees(-1), chargerDonnees(-2), chargerDonnees(3)])
.then((res) => console.log("Premiere reussite :", res))
.catch((erreur) => console.error("Toutes ont echoue :", erreur)); // AggregateError
// --- Promise.resolve / Promise.reject : creer des promises immediates ---
const promiseImmediate = Promise.resolve(42);
promiseImmediate.then((v) => console.log(v));
// --- Erreurs dans un .then() : automatiquement transformees en rejet ---
chargerDonnees(1)
.then((donnees) => {
throw new Error("Erreur volontaire dans le then"); // capturee par le .catch suivant
})
.catch((erreur) => console.error("Capturee :", erreur.message));
// --- Piege classique : oublier de retourner une Promise dans un .then() ---
function chainageCasse() {
return chargerDonnees(1).then((res) => {
chargerDonnees(res.id + 1); // BUG : pas de "return" -- la chaine ne l'attend pas !
});
}
function chainageCorrect() {
return chargerDonnees(1).then((res) => {
return chargerDonnees(res.id + 1); // correct : la Promise est propagee dans la chaine
});
}
// --- Promisifier une API basee sur des callbacks classiques ---
function delai(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function attendreEtLog() {
await delai(500);
console.log("500ms plus tard");
}Résumé
- Une Promise a 3 états : pending, fulfilled, rejected — irréversible une fois établi.
Promise.alléchoue au premier rejet ;allSettledattend tout sans jamais rejeter ;race/anyrépondent au premier événement.- Oublier un
returndans un.then()casse silencieusement la chaîne d'attente. - Une erreur levée (
throw) dans un.then()est automatiquement convertie en rejet, capturable par.catch().
Exercices pratiques
Mission : le profil utilisateur qui retourne undefined
Objectif : Corriger un return manquant dans une chaîne de then, et choisir le bon combinateur de Promises selon la tolérance à l'échec.
Contexte
Chez Technologik, chargerProfilComplet(id) doit enchaîner trois appels réseau : utilisateur, préférences, statistiques. Le développeur constate que la fonction retourne toujours undefined, alors que les requêtes réussissent (visibles dans les DevTools) :
function chargerProfilComplet(id) {
return chargerUtilisateur(id).then((utilisateur) => {
chargerPreferences(utilisateur.id).then((prefs) => {
return { ...utilisateur, prefs };
});
});
}Une fois ce bug réglé, le produit demande d'afficher le profil même si les statistiques échouent, sans jamais faire planter l'affichage des infos de base.