Apprenez en lisant dans l'ordre

async / await — attendre le résultat d'une Promise ligne après ligne

Attendre une Promise avec async / await, gérer les échecs avec try / catch, comparer la série et Promise.all, et voir l'effet d'un await oublié.

Avec le then d'une Promise, tu ne récupères la valeur que comme argument d'une fonction. Pour réutiliser le premier résultat dans une troisième étape, il faut imbriquer les fonctions ou recopier la valeur dans une variable extérieure, et le code devient vite difficile à lire.

Cet article présente async / await, qui attend un résultat avant de passer à la ligne suivante, ainsi que les façons d'attendre plusieurs Promises.

Écrire les étapes de haut en bas — fonctions async et await

Imagine que tu réserves une place libre dans un parking et que tu veux mettre le numéro attribué dans un message. Avec then, tout ce qui utilise ce numéro doit se trouver dans la fonction passée à then, et le code écrit en dessous, hors de cette fonction, s'exécute avant que le numéro n'arrive.

Dans une fonction async (une fonction déclarée avec async, qui renvoie toujours une Promise quand on l'appelle), tu peux utiliser await. await met en pause uniquement la suite de cette fonction jusqu'à ce que le résultat soit fixé, puis en extrait la valeur fulfilled. Pendant cette pause, l'appelant reçoit tout de suite une Promise pending.

// Renvoie une Promise qui passe à fulfilled avec value après ms millisecondes (comme dans l'article précédent)
function delay(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}
const assignSpot = () => delay(100, "B2-17");

// On écrit await dans une fonction marquée async
async function showParking() {
  console.log("Recherche d'une place libre");  // Recherche d'une place libre
  const spot = await assignSpot();            // Attend fulfilled avant la ligne suivante
  console.log(`Place ${spot} réservée`);      // Place B2-17 réservée
  return `Tu peux te garer à la place ${spot}`; // La Promise déjà renvoyée passe à fulfilled avec ce message
}

// L'appelant attend lui aussi la Promise renvoyée avec await
const message = await showParking();
console.log(message);                         // Tu peux te garer à la place B2-17
Ce qu'attend chacun des deux await
L'appelant — const message = await showParking()
  • showParking() s'exécute jusqu'au premier await qu'elle contient, puis renvoie une Promise pending
  • L'exécution n'atteint pas console.log(message) tant que cette Promise n'est pas fulfilled
Dans async function showParking()
  • Affiche Recherche d'une place libre
  • await assignSpot() — en pause jusqu'au passage à fulfilled, 100 ms plus tard
  • Passe à la ligne suivante une fois que spot contient "B2-17"
  • Le message renvoyé par return fait passer la Promise renvoyée à fulfilled
Celui de l'intérieur attend le résultat de assignSpot, celui de l'extérieur attend la fin de la fonction. La valeur renvoyée par return arrive dans message.

Une fonction async sans return renvoie quand même une Promise, qui passe à fulfilled avec undefined quand le corps de la fonction arrive à sa fin. Même si tu n'as pas besoin de valeur, mettre await devant l'appel fait attendre la ligne suivante jusqu'à ce que le travail à l'intérieur soit terminé.

Hors d'une fonction, await ne marche qu'à certains endroits

L'environnement d'exécution de cette page enveloppe tout ton code dans une fonction async, donc tu peux aussi écrire await hors d'une fonction, comme dans const message = await showParking();. Dans un fichier JavaScript ordinaire, un await écrit hors d'une fonction async peut provoquer une SyntaxError. Quand tu écris du code ailleurs que sur cette page, garde tes await à l'intérieur de fonctions async.

Tu envoies une notification de rendez-vous pour un salon de coiffure. delay, confirmBooking et registerReminder sont déjà déclarés.

① Transforme notifyBooking en fonction capable d'attendre des résultats.

② Attends la date et l'heure confirmées, puis affiche « Ton rendez-vous : 14 sept. à 15h00 ».

③ Enregistre un rappel pour cette date et affiche le résultat.

④ Hors de la fonction, attends qu'elle se termine, puis affiche « Ta réservation est terminée ».

(Si tout s'exécute correctement, une explication apparaîtra.)

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Traiter les échecs comme des exceptions — await et try / catch

Quand tu bloques une place de concert puis que tu paies par carte, le paiement peut échouer. Extraire les valeurs avec await ne construit pas de chaîne, donc tu ne peux pas intercepter l'échec avec un .catch en bout de chaîne comme dans l'article précédent.

Si tu fais un throw dans une fonction async, la Promise renvoyée par cette fonction devient rejected. Quand tu fais await sur une Promise rejected, la même Error est levée sur la ligne de cet await, donc il suffit d'entourer cette ligne d'un try / catch.

// delay est la même que dans le code de la première section
const holdSeat = () => delay(100, "S-204");
const sendReceipt = (seatId) => delay(100, `Reçu envoyé pour la place ${seatId}`);

// Un throw dans une fonction async rend rejected la Promise qu'elle renvoie
async function payByCard() {
  await delay(100);                          // Attend juste 100ms, sans utiliser de valeur
  throw new Error("La carte a expiré");
}

try {
  const seatId = await holdSeat();
  console.log(`Place ${seatId} bloquée`);            // Place S-204 bloquée
  await payByCard();                                 // Rejected : cette ligne lève une exception
  const receipt = await sendReceipt(seatId);         // Rien ne s'exécute à partir d'ici
  console.log(receipt);
} catch (error) {
  console.log(`Achat impossible : ${error.message}`); // Achat impossible : La carte a expiré
}
La ligne exécutée ensuite, en cas de succès et d'échec
Si le paiementpasseawait payByCard()se termineLigne suivante :sendReceiptEnvoiele reçuSi la carteest expiréeawait payByCard()lève une exceptionSaute la findu trycatch (error)affiche le message
La rangée du haut passe à la ligne suivante ; celle du bas saute la fin du try et passe directement au catch. La suite dépend de l'état de la Promise attendue.

catch ne reçoit que les exceptions levées entre les accolades du try, donc place à l'intérieur du try toute ligne await susceptible d'échouer. Le nettoyage qui doit s'exécuter que l'appel réussisse ou non va dans un bloc finally, après le try / catch.

Tu prolonges des livres empruntés à la bibliothèque. delay, loans et renewLoan sont déjà déclarés.

① Dans requestRenewal, attends le résultat de renewLoan et affiche le message obtenu.

② En cas d'échec, affiche « Prolongation impossible : » suivi du message de l'Error.

③ Que ça réussisse ou non, termine par une ligne du type « Demande traitée pour Initiation aux statistiques ».

④ Attends que le premier livre de loans soit traité avant de faire la demande pour le deuxième.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Lancer trois appels en même temps — série ou Promise.all

Imagine une fiche produit qui charge, à l'ouverture, le stock disponible, le nombre d'avis et la date de livraison estimée. Si tu écris un await par ligne, chaque requête attend que la précédente soit terminée pour démarrer, alors qu'aucune ligne n'utilise le résultat de la précédente. Des requêtes sans aucun rapport entre elles se retrouvent à faire la queue.

Lancer la tâche suivante seulement quand la précédente est terminée, c'est une exécution en série ; en lancer plusieurs d'un coup puis attendre, c'est une exécution en parallèle. Pour passer en parallèle, appelle d'abord les trois fonctions pour obtenir leurs Promises, passe-les au Promise.all de l'article précédent, et attends avec await au lieu de then.

// delay est la même que dans le code de la première section
const loadStock = () => delay(120, 8);
const loadReviews = () => delay(50, 23);
const loadArrival = () => delay(90, "13 sept.");

// En série : on appelle le suivant quand le précédent est arrivé (260ms au total : 120 + 50 + 90)
const stock = await loadStock();
const reviews = await loadReviews();
const arrival = await loadArrival();
console.log([stock, reviews, arrival].join(" / "));  // 8 / 23 / 13 sept.

// En parallèle : on appelle les trois d'abord, puis on attend (120ms, le temps du plus lent)
const promises = [loadStock(), loadReviews(), loadArrival()];
const results = await Promise.all(promises);
console.log(results.join(" / "));                   // 8 / 23 / 13 sept. (même résultat)
Quand les résultats arrivent, en série et en parallèle
Série 0 msappelle le stock120 ms stock reçuappelle les avis170 ms avis reçusappelle la date260 msles 3 sont làParallèle 0 msappelle les 350 msavis reçus90 msdate reçue120 ms stock reçules 3 sont là
Dans la rangée du bas, les trois démarrent à 0 ms et sont tous arrivés à 120 ms, le temps du plus lent. Avec un await par ligne, l'attente totale est de 260 ms.

Si tu écris await dans le corps d'un for...of, les éléments du tableau sont eux aussi traités en série, un par un, et l'attente totale est la somme de celles de chaque élément. Le tableau ci-dessous indique quand choisir la série et quand choisir le parallèle.

SituationComment l'écrirePourquoi
Charger le stock, le nombre d'avis et la date de livraisonawait Promise.all (parallèle)Aucun n'utilise le résultat d'un autre, ils peuvent donc démarrer ensemble
Envoyer un e-mail de confirmation après avoir obtenu le numéro de commandeUn await par ligne (série)Le deuxième appel a besoin du résultat du premier
Traiter les éléments d'un tableau un par un, dans l'ordreawait dans le corps d'un for...of (série)En parallèle, les éléments peuvent se terminer dans un autre ordre que celui du tableau

Une appli météo charge les prévisions de trois villes. Compare l'ordre dans lequel elles arrivent. delay, loadForecast et cities sont déjà déclarés.

① En suivant l'ordre de cities, attends que les prévisions d'une ville arrivent avant de charger la suivante.

② Affiche « Villes chargées une par une ».

③ Cette fois, lance le chargement des trois villes en même temps et attends qu'elles soient toutes arrivées.

④ Affiche les prévisions reliées par « / ».

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Appeler sans await — une Promise se retrouve dans la variable

Imagine que tu affiches le solde de points d'un membre et que tu lui proposes un avantage à partir de 1000 points. Si tu oublies await en appelant une fonction async, aucune SyntaxError ne se produit : tu obtiens simplement un mauvais texte et une comparaison fausse, ce qui rend l'erreur difficile à localiser.

Une fonction async ne renvoie pas la valeur que tu écris après return ; elle renvoie une Promise qui passe à fulfilled avec cette valeur. Si tu récupères le résultat sans await, la variable contient la Promise, et l'insérer dans un littéral de gabarit affiche [object Promise] (ce que donne une Promise convertie en chaîne).

// delay est la même que dans le code de la première section
async function loadPoints() {
  await delay(100);
  return 1200;                              // La Promise déjà renvoyée passe à fulfilled avec 1200
}

// Sans await, points contient une Promise
const points = loadPoints();
console.log(`Solde de points : ${points}`);  // Solde de points : [object Promise]
console.log(points >= 1000);                // false (une Promise convertie en nombre donne NaN, la comparaison échoue)

// Avec await, la variable contient la valeur fulfilled
const balance = await loadPoints();
console.log(`Solde de points : ${balance}`); // Solde de points : 1200
console.log(balance >= 1000);               // true
points reste une Promise
points =loadPoints()Contient unePromise pendingDevient fulfilledaprès 100 mspoints resteune PromiseInsérée dansune chaîneAffiche[object Promise]
Même une fois la Promise fulfilled, points n'est jamais remplacé par 1200. Pour obtenir la valeur, il faut await.

Si l'affichage donne [object Promise], ou si une condition censée être vraie donne false, vérifie que l'appel de la fonction async a bien son await. Dans la condition d'un if, une Promise est un objet, donc truthy : la condition est vraie quelle que soit la valeur renvoyée par la fonction.

Sans await, l'échec n'arrive jamais au catch

Dans un try, si tu appelles sans await une fonction async qui renvoie une Promise rejected, cette ligne ne lève pas d'exception et catch ne s'exécute pas. Personne ne récupère l'échec, et l'environnement d'exécution de cette page n'affiche rien du tout. Pour le recevoir dans catch, ajoute await à l'appel.

Tu vérifies si des bons de réduction ont expiré et tu indiques lesquels sont utilisables. delay, coupons et isExpired sont déjà déclarés.

① Sans await, appelle isExpired avec le premier bon et utilise le résultat comme condition d'un if.

② Si la condition est vraie, affiche « AUTUMN10 a expiré », sinon « AUTUMN10 est utilisable ».

③ Parcours coupons dans l'ordre, attends chaque résultat, puis fais la même vérification et affiche le message.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Dans un try, que deviennent les lignes qui suivent un await dont la Promise devient rejected ?

Question 2Si tu attends avec await, une ligne à la fois, trois appels qui prennent 70 ms, 30 ms et 50 ms, combien de temps faut-il environ pour que les trois résultats soient arrivés ?

Question 3Si tu appelles sans await une fonction async qui se termine par return 1200; et que tu stockes le résultat dans une variable, que contient la variable ?