Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
JavaScript

18.4 Les promesses (Promise, then, catch, Promise.all)

Les promesses (Promise, then, catch, Promise.all). Dans cette leçon du cours JavaScript, vous apprendrez à : • expliquer ce qu'est une promesse et ses trois états ; • créer une promesse avec new Promi...

0 chapitres 0 QCM Accès gratuit
Dans cet article

À propos de ce cours

Les promesses (Promise, then, catch, Promise.all). Dans cette leçon du cours JavaScript, vous apprendrez à : • 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.

Chapitre 18 — La programmation asynchrone · Leçon 4 : Les promesses (Promise, then, catch, Promise.all)

🎯 Objectifs de la leçon
• 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.

Les trois états d'une promesse : pending, fulfilled, rejected
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): f reçoit la valeur quand la promesse est tenue ;
  • .catch(f): f reçoit l'erreur quand elle est rejetée ;
  • .finally(f): f s'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
ℹ️ À retenir
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).

⚠️ Attention
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éthodeRéussit quand…Échoue quand…Usage typique
Promise.alltoutes réussissent (tableau des valeurs)une seule échoue (dès le premier échec)Charger tout ce qui est indispensable à une page
Promise.allSettledtoujours, quand toutes sont réglées (bilan de chacune)jamaisEnvoyer 10 e-mails et savoir lesquels sont partis
Promise.racela première réglée est un succèsla première réglée est un échecImposer un délai maximal (timeout)
Promise.anyune au moins réussit (la première)toutes échouentInterroger 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));
⚠️ Attention
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.
ℹ️ À retenir
• 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) :

  1. Réécrivez rechercherLivre, verifierDisponibilite et reserver pour qu'elles renvoient une promesse. Que devient le paramètre callback? Que remplace callback(err)?
  2. Enchaînez les trois appels avec then. Combien de niveaux d'indentation obtenez-vous maintenant ? Où placez-vous la gestion d'erreur ?
  3. Si le livre n'est pas disponible, la réservation doit être refusée. Faut-il faire throw new Error(…) ou return Promise.reject(…) dans le then? Testez les deux : y a-t-il une différence de résultat ?
  4. Retirez un return dans un des then. Que reçoit le then suivant ? L'ordre d'affichage change-t-il ?
  5. 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 » ?
  6. Écrivez avecDelaiMax(promesse, ms) qui rejette avec « Trop long » si la promesse n'est pas réglée en ms millisecondes. Quelle méthode statique de Promise utilisez-vous ?
  7. Dans la console du navigateur, utilisez fetch sur https://jsonplaceholder.typicode.com/todos/1 puis sur …/todos/999999. Le catch est-il appelé pour la seconde URL ? Pourquoi ?

Je vérifie / je rends :

  • ☐ Le fichier bibliotheque-promesses.js.
  • ☐ La fonction avecDelaiMax testé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 →