Question 1Dans un try, que deviennent les lignes qui suivent un await dont la Promise devient rejected ?
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
showParking()s'exécute jusqu'au premierawaitqu'elle contient, puis renvoie une Promise pending- L'exécution n'atteint pas
console.log(message)tant que cette Promise n'est pas fulfilled
- 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
spotcontient"B2-17" - Le message renvoyé par
returnfait passer la Promise renvoyée à fulfilled
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.
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é
}
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.
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)
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.
| Situation | Comment l'écrire | Pourquoi |
|---|---|---|
| Charger le stock, le nombre d'avis et la date de livraison | await 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 commande | Un 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'ordre | await 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 |
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
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.
Vérification des connaissances
Répondez à chaque question une par une.
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 ?