</> HTML5Advent
ENFRESDEITPT

// js · Web Platform Advent #2

Les promesses JavaScript : guide pratique

Comprendre les promesses JavaScript — leur création, then/catch/finally, le chaînage et la combinaison avec Promise.all, race, any et allSettled — avec des exemples exécutables et les pièges courants.

Une pièce de puzzle rouge venant combler le dernier trou d'un puzzle gris

Une promesse est un objet qui représente une valeur pas encore disponible. C'est le socle du JavaScript asynchrone : fetch(), les minuteurs, les lectures de fichiers en Node et la plupart des API modernes renvoient des promesses. Une promesse est toujours dans l'un de trois états — en attente (pending), tenue (fulfilled) ou rompue (rejected) — et une fois réglée, elle ne change plus jamais.

Créer une promesse

La plupart du temps, vous consommez des promesses qu'une API vous fournit. Mais on en crée une soi-même avec le constructeur Promise, qui reçoit une fonction exécutrice avec resolve et reject :

const wait = (ms) => new Promise((resolve) => {
  setTimeout(resolve, ms);
});

const fetchUser = (id) => new Promise((resolve, reject) => {
  if (!id) reject(new Error('id requis'));
  else resolve({ id, name: 'Ada' });
});

Appeler resolve(valeur) tient la promesse avec cette valeur ; appeler reject(erreur) la rompt. Toute exception levée de façon synchrone dans l'exécutrice rompt aussi la promesse.

then, catch et finally

On lit le résultat d'une promesse avec .then() pour le succès, .catch() pour l'échec, et .finally() pour un nettoyage qui s'exécute dans les deux cas :

fetchUser(42)
  .then((user) => console.log('Reçu', user.name))
  .catch((err) => console.error('Échec :', err.message))
  .finally(() => console.log('Terminé'));

.then() peut prendre deux arguments — onFulfilled et onRejected — mais un .catch() séparé en fin de chaîne est plus clair et attrape aussi les erreurs levées dans les .then() précédents.

Le chaînage : le vrai super-pouvoir

Chaque .then() renvoie une nouvelle promesse, d'où le chaînage. Ce que vous renvoyez d'un gestionnaire devient l'entrée du suivant. Surtout, si vous renvoyez une promesse, la chaîne attend qu'elle soit réglée avant de continuer :

fetch('/api/user')
  .then((res) => res.json())          // renvoie une promesse → la chaîne attend
  .then((user) => fetch('/api/posts?user=' + user.id))
  .then((res) => res.json())
  .then((posts) => console.log(posts.length, 'articles'))
  .catch((err) => console.error(err));

Un seul .catch() à la fin gère un rejet de n'importe quelle étape au-dessus, ce qui explique pourquoi le chaînage l'emporte sur l'imbrication des callbacks.

Des sprinteuses de relais sur une piste se passant le témoin d'une coureuse à l'autre
Comme des coureuses de relais qui se passent le témoin, une chaîne de promesses transmet le résultat de chaque étape au gestionnaire suivant, dans l'ordre.

Exécuter des promesses ensemble

Quand les tâches ne dépendent pas les unes des autres, lancez-les en parallèle et combinez les résultats. Quatre méthodes statiques couvrent les cas courants :

  • Promise.all([...]) — attend que toutes soient tenues ; rompt dès qu'une est rompue. Renvoie un tableau de résultats dans l'ordre.
  • Promise.allSettled([...]) — attend que toutes se terminent et ne rompt jamais ; renvoie un tableau d'objets { status, value } ou { status, reason }.
  • Promise.race([...]) — se règle avec la première promesse réglée, qu'elle soit tenue ou rompue.
  • Promise.any([...]) — se règle avec la première promesse tenue ; ne rompt que si elles sont toutes rompues.
// Récupérer trois ressources d'un coup, échouer si l'une échoue
const [user, posts, prefs] = await Promise.all([
  fetch('/api/user').then((r) => r.json()),
  fetch('/api/posts').then((r) => r.json()),
  fetch('/api/prefs').then((r) => r.json()),
]);

// Pareil, mais en tolérant les échecs individuels
const results = await Promise.allSettled([taskA(), taskB(), taskC()]);
results.forEach((r) => {
  if (r.status === 'fulfilled') console.log('ok', r.value);
  else console.warn('échec', r.reason);
});

Pièges courants

  • Oublier le return. Dans un .then(), si vous appelez une promesse sans la return, la chaîne ne l'attend pas — vous perdez l'ordre et la gestion d'erreur.
  • Rejets non gérés. Une promesse sans .catch() (ou sans try/catch autour de await) produit un unhandledrejection. Gérez toujours les erreurs en fin de chaîne.
  • Mélanger callbacks et promesses. N'appelez pas resolve deux fois, ni resolve et reject ensemble — seul le premier appel compte ; le reste est ignoré en silence.
  • L'exécutrice s'exécute aussitôt. La fonction passée à new Promise() s'exécute de façon synchrone, immédiatement — seuls les callbacks .then() sont différés.

Aide-mémoire

ObjectifMéthode
Gérer le succès.then(onFulfilled)
Gérer l'échec.catch(onRejected)
Nettoyer dans tous les cas.finally(fn)
Toutes doivent réussirPromise.all
Récupérer chaque issuePromise.allSettled
La première régléePromise.race
La première réussiePromise.any

Les promesses sont la tuyauterie sous async/await, qui n'est qu'une syntaxe plus agréable au-dessus des mêmes objets. Une fois le chaînage et les quatre combinateurs devenus naturels, le JavaScript asynchrone cesse d'intimider.