web

JavaScript asynchrone : callbacks, promises et async/await

30 mars 2026 5 min de lecture

Pourquoi le JavaScript asynchrone est-il essentiel ?

Imagine que tu cliques sur un bouton pour charger des données depuis un serveur. Si ton code JavaScript était synchrone, toute la page se figerait en attendant la réponse du serveur. Pas très agréable pour l'utilisateur, n'est-ce pas ? C'est là qu'intervient la programmation asynchrone. Elle permet à ton code de lancer une tâche longue (comme une requête réseau, la lecture d'un fichier ou un timer) et de continuer à exécuter le reste du programme sans attendre. Une fois la tâche terminée, JavaScript revient traiter le résultat. C'est le fondement des interfaces web fluides et réactives que tu utilises tous les jours.

Les callbacks : la méthode historique (et ses pièges)

La première méthode pour gérer l'asynchrone en JavaScript a été la fonction de callback (fonction de rappel). Le principe est simple : tu passes une fonction en argument à une fonction asynchrone. Cette fonction de callback sera exécutée une fois l'opération asynchrone terminée.

Un exemple simple avec setTimeout

setTimeout est une fonction native qui exécute une fonction après un délai donné (en millisecondes). C'est un parfait exemple d'opération asynchrone.

console.log('Début du script');

setTimeout(function() {
    console.log('Ce message apparaît après 2 secondes');
}, 2000);

console.log('Fin du script (affiché immédiatement)');

Dans la console, tu verras : "Début du script", puis "Fin du script", et enfin, deux secondes plus tard, "Ce message apparaît après 2 secondes". Le programme n'a pas attendu.

Le problème de l'Enfer des Callbacks (Callback Hell)

Les callbacks deviennent problématiques quand tu dois enchaîner plusieurs opérations asynchrones qui dépendent les unes des résultats des autres. Imagine devoir : 1) récupérer des données utilisateur, 2) puis avec cet ID, récupérer ses posts, 3) puis avec le premier post, récupérer les commentaires.

getUser(1, function(user) {
    getPosts(user.id, function(posts) {
        getComments(posts[0].id, function(comments) {
            console.log(comments);
            // Et ça peut continuer encore...
        });
    });
});

Ce code, avec ses multiples niveaux d'indentation, est difficile à lire, à déboguer et à maintenir. On appelle cela le Callback Hell ou la "Pyramide de l'Enfer". Heureusement, des solutions plus élégantes existent.

Les Promises : une promesse de code plus clair

Introduites pour résoudre les problèmes des callbacks, les Promises (Promesses) représentent la complétion future (ou l'échec) d'une opération asynchrone et sa valeur résultat. Une Promise est un objet qui est dans l'un de ces trois états :

  • Pending (en attente) : état initial.
  • Fulfilled (tenue) : l'opération a réussi.
  • Rejected (rejetée) : l'opération a échoué.

Créer et consommer une Promise

Une Promise se construit avec le mot-clé new Promise. Elle prend une fonction exécutrice avec deux paramètres : resolve (appelé en cas de succès) et reject (appelé en cas d'échec).

const maPromesse = new Promise(function(resolve, reject) {
    // Opération asynchrone simulée
    setTimeout(() => {
        const succes = true; // Change ça en false pour simuler un échec
        if (succes) {
            resolve('Données récupérées avec succès !');
        } else {
            reject(new Error('Échec de la récupération'));
        }
    }, 1500);
});

Pour consommer le résultat d'une Promise, on utilise les méthodes .then() (pour le succès) et .catch() (pour l'erreur).

maPromesse
    .then(function(resultat) {
        console.log('Succès :', resultat);
    })
    .catch(function(erreur) {
        console.error('Erreur :', erreur.message);
    });

Chaîner les Promises : la solution à l'enfer des callbacks

La grande force des Promises est l'enchaînement (chaining). La méthode .then() retourne elle-même une nouvelle Promise, ce qui permet d'enchaîner les opérations de manière linéaire et lisible.

// Simulation de fonctions retournant des Promises
function getUser(id) { /* ... */ }
function getPosts(userId) { /* ... */ }
function getComments(postId) { /* ... */ }

getUser(1)
    .then(user => getPosts(user.id)) // Retourne une Promise
    .then(posts => getComments(posts[0].id)) // Retourne une Promise
    .then(comments => console.log(comments))
    .catch(err => console.error('Une erreur est survenue :', err));

Compare ce code avec le Callback Hell ! Il se lit presque comme une phrase : "Récupère l'utilisateur, PUIS ses posts, PUIS les commentaires, PUIS affiche-les. Attrape toute erreur qui surviendrait." Beaucoup plus clair, non ?

Async/Await : la syntaxe moderne et intuitive

async/await est une syntaxe introduite plus récemment qui permet d'écrire du code asynchrone comme s'il était synchrone. C'est du sucre syntaxique par-dessus les Promises qui rend le code encore plus lisible.

Les mots-clés async et await

  • async : Placé devant une fonction, il la transforme en une fonction qui retourne toujours une Promise.
  • await : Ne peut être utilisé qu'à l'intérieur d'une fonction async. Il met en pause l'exécution de la fonction jusqu'à ce que la Promise placée à sa droite soit tenue ou rejetée, et il extrait la valeur de résolution.

Réécrivons notre exemple avec async/await

async function afficherCommentaires() {
    try {
        const user = await getUser(1);       // Attend la résolution de la Promise
        const posts = await getPosts(user.id);
        const comments = await getComments(posts[0].id);
        console.log(comments);
    } catch (erreur) {
        console.error('Une erreur est survenue :', erreur);
    }
}

afficherCommentaires(); // N'oublie pas d'appeler la fonction !

Le code est encore plus simple à suivre ! Il ressemble à du code synchrone classique. La gestion des erreurs se fait avec un simple bloc try...catch, ce qui est très naturel.

Attention aux pièges !

Avec async/await, il faut éviter un écueil courant : lancer des opérations await les unes après les autres quand elles sont indépendantes. Cela crée un attente inutile.

// ❌ Lent : les requêtes sont lancées SEULEMENT l'une après l'autre.
async function exempleLent() {
    const data1 = await fetch('/api/data1'); // Attend la fin
    const data2 = await fetch('/api/data2'); // Attend le début...
}

// ✅ Rapide : les requêtes sont lancées EN PARALLÈLE.
async function exempleRapide() {
    const promise1 = fetch('/api/data1'); // Lance la requête
    const promise2 = fetch('/api/data2'); // Lance la requête immédiatement
    const data1 = await promise1; // Attend les deux résultats
    const data2 = await promise2;
    // On peut aussi utiliser Promise.all()
    // const [data1, data2] = await Promise.all([promise1, promise2]);
}

Quand utiliser quoi ? Un petit guide pratique

Tu as maintenant trois outils en main. Voici comment choisir :

  • Callbacks : Principalement pour les APIs plus anciennes (comme certaines fonctions de Node.js) ou les simples événements (addEventListener). Pour les nouvelles opérations asynchrones, privilégie les Promises.
  • Promises : La base moderne. Utilise-les quand tu fais des appels API avec fetch(), ou quand tu utilises des bibliothèques qui les retournent. Parfait pour le chaînage.
  • Async/Await : Ta syntaxe de prédilection pour la majorité de ton code métier. Elle rend la logique asynchrone extrêmement claire, surtout avec des enchaînements complexes et la gestion d'erreurs. Rappelle-toi : async/await est construit sur les Promises, il faut donc bien les comprendre.

La programmation asynchrone est un pilier du développement web moderne. En maîtrisant ces concepts, tu pourras créer des applications interactives, performantes et offrant une excellente expérience utilisateur. Alors, à toi de jouer !

📚 Pour aller plus loin

Questions fréquentes

Est-ce qu'async/await remplace complètement les Promises ?

Non, async/await est construit PAR-DESSUS les Promises. C'est une façon plus agréable de les utiliser, mais tu dois comprendre comment fonctionnent les Promises pour bien utiliser async/await. Une fonction marquée 'async' retourne toujours une Promise, et 'await' ne fonctionne que sur une Promise.

Comment gérer plusieurs opérations asynchrones en parallèle avec async/await ?

Pour lancer plusieurs opérations indépendantes en parallèle et attendre qu'elles se terminent toutes, utilise <code>Promise.all()</code>. Exemple : <code>const [result1, result2] = await Promise.all([tacheAsync1(), tacheAsync2()]);</code>. Cela est beaucoup plus rapide que d'utiliser <code>await</code> deux fois de suite.

Quelle est la différence entre .then().catch() et try...catch avec async/await ?

Fonctionnellement, ils font la même chose : ils gèrent le succès et l'échec d'une opération asynchrone. <code>.then().catch()</code> est le style des Promises, tandis que <code>try...catch</code> est utilisé avec async/await. Le bloc try...catch est souvent considéré comme plus lisible car il ressemble à la gestion d'erreur synchrone classique.

Bravo ! Tu as lu cet article
Inscris-toi pour sauvegarder ta progression et gagner des XP
Creer mon compte
async JavaScriptpromises await
Pixel