Retour au cours

frontend / javascript

Async/await

Leçon 121 exercice

Explication

Ce que vous allez apprendre

  • Réécrire une chaîne de .then() en fonction async/await équivalente
  • Expliquer ce que await suspend réellement, et ce qui continue de s'exécuter pendant ce temps
  • Capturer les erreurs d'une fonction async avec try/catch
  • Détecter une perte de parallélisme causée par des await séquentiels inutiles
  • Corriger ce cas avec Promise.all quand les opérations sont indépendantes

Dans quel contexte ?

Une page de profil met 4 secondes à s'afficher alors que chaque appel API individuel ne prend que 1 seconde. Le code contient const user = await getUser(id); const posts = await getPosts(id); const followers = await getFollowers(id); const stats = await getStats(id); : quatre requêtes indépendantes, exécutées l'une après l'autre au lieu d'en parallèle. Remplacer ces quatre lignes par const [user, posts, followers, stats] = await Promise.all([getUser(id), getPosts(id), getFollowers(id), getStats(id)]); fait chuter le temps de chargement à environ 1 seconde.

1. Un problème de lisibilité, pas de fonctionnement

Les Promises ont résolu le problème d'imbrication des callbacks, mais leur syntaxe en chaîne de .then() reste parfois difficile à suivre, surtout avec des conditions ou des boucles. async/await ne change rien au fonctionnement sous-jacent : c'est du "sucre syntaxique" au-dessus des Promises.

Prérequis

Cette leçon suppose que les trois états d'une Promise (pending, fulfilled, rejected) et le chaînage .then()/.catch() de la leçon précédente sont bien compris : async/await s'appuie exactement sur le même mécanisme, avec une autre syntaxe.

2. Écrire de l'asynchrone qui se lit comme du synchrone

Ce sucre syntaxique permet d'écrire du code asynchrone qui se LIT comme du code synchrone, ligne après ligne, ce qui réduit considérablement la charge mentale par rapport à une chaîne de .then().

3. Ce que fait vraiment await

Le mot-clé await suspend l'exécution de la fonction à cet endroit précis, jusqu'à ce que la Promise concernée se résolve, puis reprend avec le résultat directement disponible. Contrairement à ce que le mot "attendre" pourrait laisser penser, seule la fonction courante est mise en pause : le reste du programme continue de tourner normalement.

4. async retourne toujours une Promise

Une fonction déclarée async retourne toujours une Promise, même sans await explicite. Une erreur levée à l'intérieur se transforme automatiquement en rejet, capturable avec un try/catch classique — plus naturel qu'une chaîne de .catch().

StyleGestion des erreursLisibilité en séquence
.then()/.catch()Un .catch() par chaînePeut devenir dense avec des conditions
async/awaittry/catch classiqueSe lit comme du code synchrone

5. Le piège le plus fréquent de cette syntaxe

Écrire await deux fois de suite pour des opérations indépendantes les exécute en séquence, doublant le temps total, alors qu'un Promise.all les lancerait en même temps. C'est une perte de parallélisme facile à ne pas remarquer.

Piège fréquent

const a = await taskA(); const b = await taskB(); exécute taskB seulement après la fin de taskA, même si elles n'ont aucun lien. Si les deux sont indépendantes, lancez-les d'abord (const pA = taskA(); const pB = taskB();) puis attendez-les ensemble (const [a, b] = await Promise.all([pA, pB]);).

6. La bonne question à se poser

Il faut donc distinguer les cas où l'attente séquentielle est réellement nécessaire (chaque étape dépend du résultat de la précédente) de ceux où elle ne l'est pas — et dans ce second cas, préférer systématiquement Promise.all.

Commandes & code

Async/await

La syntaxe qui rend le code asynchrone aussi lisible que du code synchrone.

js
function delai(ms, valeur) {
    return new Promise((resolve) => setTimeout(() => resolve(valeur), ms));
}

// async transforme une fonction pour qu'elle retourne TOUJOURS une Promise
async function chargerUtilisateur(id) {
    if (id <= 0) throw new Error("ID invalide");     // equivalent a reject()
    const donnees = await delai(300, { id, nom: `User${id}` });   // suspend jusqu'a resolution
    return donnees;                                    // equivalent a resolve(donnees)
}

chargerUtilisateur(1).then(console.log);       // async function reste utilisable avec .then()

// --- await : uniquement utilisable dans une fonction async (ou au top-level des modules ES) ---
async function main() {
    console.log("Debut");
    const utilisateur = await chargerUtilisateur(1);    // le code suivant ATTEND la resolution
    console.log("Utilisateur recu :", utilisateur);
    console.log("Fin");
}
main();

// --- Gestion d'erreur : try/catch remplace .catch() ---
async function chargerAvecGestionErreur(id) {
    try {
        const utilisateur = await chargerUtilisateur(id);
        return utilisateur;
    } catch (erreur) {
        console.error("Erreur capturee :", erreur.message);
        return null;
    } finally {
        console.log("Nettoyage, dans tous les cas");
    }
}
chargerAvecGestionErreur(-1);

// --- PIEGE : await sequentiel involontaire (perte de parallelisme) ---
async function sequentielLent() {
    const debut = Date.now();
    const a = await delai(500, "A");     // attend 500ms...
    const b = await delai(500, "B");      // ...PUIS attend encore 500ms -- total 1000ms
    console.log(`Sequentiel : ${Date.now() - debut}ms`);
    return [a, b];
}

// --- Correction : lancer en parallele avec Promise.all ---
async function parallele() {
    const debut = Date.now();
    const [a, b] = await Promise.all([delai(500, "A"), delai(500, "B")]);   // total ~500ms
    console.log(`Parallele : ${Date.now() - debut}ms`);
    return [a, b];
}

// --- await dans une boucle : sequentiel par nature (souvent voulu, parfois un piege) ---
async function traiterSequentiellement(ids) {
    const resultats = [];
    for (const id of ids) {
        resultats.push(await chargerUtilisateur(id));    // un a la fois, dans l'ordre
    }
    return resultats;
}

async function traiterEnParallele(ids) {
    // toutes les requetes partent EN MEME TEMPS
    return Promise.all(ids.map((id) => chargerUtilisateur(id)));
}

// --- for await...of : consommer des Promises OU des async generators ---
async function* flux() {
    yield delai(100, "a");
    yield delai(100, "b");
    yield delai(100, "c");
}
async function consommerFlux() {
    for await (const valeur of flux()) {
        console.log("Recu :", valeur);
    }
}
consommerFlux();

// --- Top-level await (ES2022, dans les modules ES uniquement) ---
// fichier module.mjs :
// const config = await fetch("/config.json").then((r) => r.json());
// export default config;

// --- Combiner async/await avec allSettled pour ne jamais interrompre le traitement ---
async function chargerTousLesUtilisateurs(ids) {
    const resultats = await Promise.allSettled(ids.map(chargerUtilisateur));
    return resultats
        .filter((r) => r.status === "fulfilled")
        .map((r) => r.value);
}

Résumé

  • async/await est du sucre syntaxique au-dessus des Promises : lisibilité, pas performance.
  • Une fonction async retourne toujours une Promise ; try/catch remplace .catch().
  • await séquentiel dans du code indépendant fait perdre le parallélisme : préférer Promise.all.
  • for await...of consomme aussi bien un tableau de Promises qu'un générateur asynchrone.

Exercices pratiques

1 disponible
1

Mission : la page de profil qui met 4 secondes à charger

Objectif : Diagnostiquer une perte de parallélisme causée par des await séquentiels inutiles, et corriger avec Promise.all sans casser l'ordre de dépendance réelle.

Contexte

La page de profil de Technologik met 4 secondes à s'afficher alors que chaque appel API individuel prend 1 seconde. Le code :

js
async function chargerProfil(id) {
  const user = await getUser(id);
  const posts = await getPosts(id);
  const followers = await getFollowers(id);
  const stats = await getStats(id);
  return { user, posts, followers, stats };
}

Aucune de ces quatre requêtes ne dépend du résultat d'une autre : elles pourraient toutes partir en même temps. Tu dois corriger la fonction pour qu'elle charge tout en parallèle, sans changer la forme de l'objet retourné.

Résoudre l’exercice →