Créa-blog

#100JoursPourCoder
Projet Créa-code

Ressources pour développeur web

Théme de la semaine : Découvrir node.js

Asynchronisme en JavaScript : du callback à async/await

Temps de lecture estimé : 8 minutes
Accueil Javascript Asynchronisme en JavaScript : du callback à async/await

Lorsque l’on débute en développement web, notamment avec JavaScript ou node.js, on rencontre assez vite un mot qui semble à la fois flou et intimidant : l’asynchrone. Ce terme revient souvent lorsque l’on parle d’appels API, de lecture de fichiers, de gestion d’événements, ou encore de requêtes réseau. Pourtant, derrière ce mot se trouve une idée simple : pendant qu’une action longue se déroule, votre programme continue d’avancer au lieu de rester bloqué. L’asynchronisme permet donc de gagner en fluidité, en rapidité et en efficacité.

  • Comprendre enfin comment fonctionne l’asynchronisme en JavaScript pour écrire du code plus fluide et éviter les blocages dans node.js.
  • Savoir choisir entre callbacks, Promises et async/await pour rendre son code plus clair, lisible et facile à maintenir.
  • Acquérir une vision concrète de l’Event Loop afin d’anticiper le comportement du code et déboguer plus efficacement.

Mais comprendre comment fonctionne cet asynchronisme en JavaScript peut parfois donner l’impression de devoir démêler un sac de nœuds. Il existe plusieurs manières d’écrire du code asynchrone : avec des callbacks, des Promises, et enfin async/await.

Chaque approche correspond à un moment de l’évolution du langage et à une manière différente de penser l’exécution du code.

Dans ce guide, nous allons y aller pas à pas. Nous allons partir de zéro, sans prérequis particulier, et explorer ensemble comment et pourquoi l’asynchronisme est utilisé. Nous prendrons le temps d’expliquer chaque concept, avec des exemples concrets et accessibles aux débutants. L’objectif n’est pas simplement d’apprendre une syntaxe, mais de comprendre ce qui se passe réellement sous le capot lorsque l’on exécute du code en JavaScript, que ce soit dans le navigateur ou dans un environnement node.js.

Pourquoi l’asynchronisme existe-t-il ? Qu’est ce qu’un code asynchrone ?

Avant de voir la pratique, il faut d’abord comprendre pourquoi l’asynchronisme est indispensable, notamment dans node.js.

JavaScript est un langage à un seul fil d’exécution. Cela signifie qu’à un instant donné, un seul morceau de code est exécuté. Imaginez un cuisinier qui travaille seul. Si ce cuisinier doit rester debout, immobile, à attendre que de l’eau bouille avant de faire autre chose, la cuisine n’avancera pas très vite. Idéalement, il met l’eau à chauffer, puis pendant ce temps, il coupe des légumes, fait mariner quelque chose, prépare la sauce. Quand l’eau bout, il y revient. Ce cuisinier travaille en asynchrone.

JavaScript fait exactement la même chose. Lorsqu’il doit exécuter une opération longue, il la confie à l’environnement d’exécution (par exemple node.js ou le navigateur), qui s’en charge pendant que JavaScript continue le reste des instructions.

Mais si JavaScript délègue une tâche, comment récupérer le résultat une fois qu’elle est terminée ? C’est ici qu’intervient la notion de callback, notre premier outil pour gérer l’asynchronisme.

Les callbacks, éléments essentiels d’un code asynchrone

Un callback, c’est tout simplement une fonction que l’on donne en argument à une autre fonction.

Cette fonction sera appelée plus tard, lorsque l’action asynchrone aura terminé. C’est un peu comme laisser votre numéro de téléphone à la boulangerie lorsqu’ils n’ont plus de baguettes : ils vous appellent quand c’est prêt.

Voyons un exemple simple. Imaginons que l’on veuille afficher un message après un certain délai :

console.log("Début");

setTimeout(function() {
  console.log("Message affiché après 2 secondes");
}, 2000);

console.log("Fin");

Si vous exécutez ce code, l’ordre d’affichage sera :

Début
Fin
Message affiché après 2 secondes

Cela peut surprendre, car l’appel à setTimeout vient entre « Début » et « Fin ». Mais comme setTimeout est asynchrone, JavaScript n’attend pas la fin des deux secondes pour continuer.

Un callback n’est donc rien d’autre qu’une fonction appelée plus tard. C’est simple.

Cependant, les choses deviennent plus compliquées lorsque nous devons enchaîner plusieurs actions asynchrones. Imaginons que nous ayons besoin de :

  • Lire un fichier
  • Puis transformer son contenu
  • Puis l’envoyer à un serveur

Si nous écrivons cela avec des callbacks imbriqués, nous arrivons à ce que l’on appelle le callback hell :

faireEtape1(function(resultat1) {
  faireEtape2(resultat1, function(resultat2) {
    faireEtape3(resultat2, function(resultatFinal) {
      console.log("Tout est terminé");
    });
  });
});

On se retrouve vite avec du code difficile à lire, à modifier ou à déboguer. Pour résoudre ce problème, JavaScript a introduit les Promises.

Les Promises

Une Promise, c’est un objet qui représente une valeur qui n’est pas encore disponible mais qui le sera dans le futur. Elle est soit en attente, soit accomplie, soit rejetée en cas d’erreur.

Cela permet d’enchaîner les actions de manière plus lisible :

faireEtape1()
  .then(function(resultat1) {
    return faireEtape2(resultat1);
  })
  .then(function(resultat2) {
    return faireEtape3(resultat2);
  })
  .then(function(resultatFinal) {
    console.log("Tout est terminé");
  })
  .catch(function(erreur) {
    console.error("Une erreur s'est produite :", erreur);
  });

L’avantage principal des Promises est la lisibilité. On n’a plus une cascade de fonctions imbriquées, mais une chaîne d’actions.

Cependant, il y a encore une syntaxe particulière à mémoriser. C’est ici qu’arrive async/await, introduit plus récemment, qui permet d’écrire du code asynchrone comme s’il était synchrone.

async/await

async/await n’est pas une nouvelle manière de gérer l’asynchronisme. C’est une manière plus simple d’écrire du code basé sur des Promises.

Le mot clé async permet de déclarer une fonction asynchrone. Le mot clé await sert à attendre qu’une Promise soit résolue avant de continuer.

Voici l’exemple précédent réécrit avec async/await :

async function traiterEtapes() {
  try {
    const resultat1 = await faireEtape1();
    const resultat2 = await faireEtape2(resultat1);
    const resultatFinal = await faireEtape3(resultat2);
    console.log("Tout est terminé");
  } catch (erreur) {
    console.error("Une erreur s'est produite :", erreur);
  }
}

traiterEtapes();

L’avantage est immédiat : le code est plus clair, plus linéaire et plus naturel à lire.

Nous allons continuer dans la suite la structure interne de l’Event Loop, comprendre comment node.js orchestre l’asynchronisme, explorer des cas pratiques, réaliser des exemples complets et voir comment choisir entre callback, Promise et async/await selon les situations.

Pour allez plus loin : Async/Await VS Promise

Ce qui se passe vraiment en coulisses – l’Event Loop

Pour vraiment comprendre l’asynchronisme, il faut jeter un œil à ce qui se passe en arrière-plan. Beaucoup de développeurs débutants imaginent encore que JavaScript se comporte comme d’autres langages exécutant tout leur code étape par étape sans interruption. Pourtant, la réalité est bien différente, et c’est ce qui rend JavaScript si efficace.

Vous souvenez-vous lorsque nous avons parlé du cuisinier qui met l’eau à chauffer pendant qu’il coupe les légumes ? Cette image illustre parfaitement la manière dont la boucle d’événements, que l’on appelle Event Loop, fonctionne.

JavaScript exécute d’abord tout le code synchrone. Pendant ce temps, les opérations asynchrones (comme une requête réseau, une lecture de fichier avec node.js, un minuteur avec setTimeout) sont prises en charge par l’environnement d’exécution. Une fois terminées, elles indiquent à JavaScript qu’elles sont prêtes. L’information est placée dans une file d’attente. Et c’est l’Event Loop qui se charge de vérifier régulièrement si quelque chose dans cette file d’attente est prêt à être exécuté.

On peut résumer l’idée ainsi : votre programme continue d’avancer, mais garde en mémoire qu’il doit revenir sur certains points lorsque des tâches auront terminé.

Formation web et informatique - Alban Guillier - Formateur

Des formations informatique pour tous !

Débutant ou curieux ? Apprenez le développement web, le référencement, le webmarketing, la bureautique, à maîtriser vos appareils Apple et bien plus encore…

Formateur indépendant, professionnel du web depuis 2006, je vous accompagne pas à pas et en cours particulier, que vous soyez débutant ou que vous souhaitiez progresser. En visio, à votre rythme, et toujours avec pédagogie.

Découvrez mes formations Qui suis-je ?

Un exemple très simple peut aider à se représenter ça.

console.log("1");

setTimeout(() => {
  console.log("2");
}, 0);

console.log("3");

Si vous exécutez ce code, vous verrez l’ordre suivant :

1
3
2

Même si nous indiquons un délai de 0 milliseconde, le message « 2 » arrive à la fin. Pourquoi ? Parce que l’Event Loop fait passer les tâches asynchrones après l’exécution du code principal. JavaScript ne stoppe jamais en plein milieu de ce qu’il fait.

Cette mécanique permet au langage d’être très réactif, ce qui est précieux dans le navigateur, mais aussi dans node.js où l’on peut gérer des milliers de connexions simultanées sans bloquer l’application.

Exemple concret en node.js

Prenons maintenant un exemple qui parle aux développeurs qui commencent avec node.js. Imaginons que nous devions lire un fichier puis afficher son contenu. En synchrone, cela bloquerait tout le programme pendant la lecture. En asynchrone, node.js va gérer l’opération sans interrompre le reste.

En synchrone :

const fs = require("fs");

console.log("Lecture du fichier...");

const contenu = fs.readFileSync("texte.txt", "utf8");
console.log(contenu);

console.log("Fin du programme");

Dans ce cas, node.js attend la fin de la lecture avant de continuer. Si le fichier est très lourd, tout s’arrête.

Voici la version asynchrone :

const fs = require("fs");

console.log("Lecture du fichier...");

fs.readFile("texte.txt", "utf8", (err, contenu) => {
  if (err) {
    console.error("Erreur :", err);
    return;
  }
  console.log(contenu);
});

console.log("Fin du programme");

Résultat :

Lecture du fichier...
Fin du programme
(contenu du fichier)

Comme pour setTimeout, le callback est simplement mis en attente tant que node.js gère la lecture du fichier. Le programme peut continuer sans attendre.

Avec Promises :

const fs = require("fs").promises;

async function lire() {
  console.log("Lecture du fichier...");
  const contenu = await fs.readFile("texte.txt", "utf8");
  console.log(contenu);
  console.log("Fin du programme");
}

lire();

Ici, cela ressemble presque à du code classique. On retrouve immédiatement un sentiment de contrôle. C’est l’un des grands atouts du couple async/await.

Pour les débutants avec Node.js : Comprendre et installer Node.js

Quand utiliser callbacks, Promises ou async/await ?

Si vous travaillez avec du code ancien, vous verrez encore beaucoup de callbacks. Ils fonctionnent très bien, mais peuvent vite devenir difficiles à lire. Les Promises ont permis d’améliorer la lisibilité, surtout lorsqu’il s’agit de chaîner plusieurs opérations. Elles sont toujours très utilisées, surtout pour la gestion d’erreurs ou dans des librairies tierces.

Cependant, dans la plupart des nouveaux projets ou lors de l’apprentissage, async/await est généralement le choix le plus confortable. Il permet d’écrire du code plus clair, plus linéaire, et surtout plus naturel à comprendre.

On peut retenir une idée simple :

  • Les callbacks représentent l’origine de l’asynchronisme.
  • Les Promises ont simplifié la manière de gérer les résultats et les erreurs.
  • async/await a rendu tout cela lisible et intuitif.

J’ai travaillé sur un script node.js qui devait récupérer des données depuis plusieurs API en chaîne. J’avais écrit tout cela avec des callbacks imbriqués, pensant que le résultat serait propre. Tout fonctionnait… jusqu’au jour où j’ai dû modifier une partie du script. Je me suis retrouvé face à un bloc de code impossible à relire, comme un paragraphe dans lequel chaque phrase dépendait d’une parenthèse différente. J’ai passé plus de temps à comprendre ce que j’avais écrit qu’à corriger mon code. Ce jour-là, j’ai compris que la lisibilité n’est pas un luxe, mais une nécessité. Plus notre code est clair, plus il est robuste, maintenable et agréable à développer.

L’erreur fréquente des débutants

Une confusion arrive souvent : confondre asynchrone et « plus rapide ». Une fonction asynchrone n’est pas forcément plus rapide. Elle est simplement non bloquante. Elle laisse le reste du programme continuer.

Un exemple simple : attendre la réponse d’un serveur distant prendra toujours du temps. Mais au lieu de geler le programme, l’asynchronisme permet au programme d’afficher quelque chose à l’écran, de traiter d’autres données, de répondre à une autre requête. Votre application reste vivante.

Construisons un exemple complet

Imaginons une petite fonction node.js qui récupère des données sur une API, transforme ces données et les affiche.

Avec async/await, cela ressemble à quelque chose comme ceci :

async function traiterDonnees() {
  try {
    console.log("Récupération des données...");
    const reponse = await fetch("https://jsonplaceholder.typicode.com/posts");
    const donnees = await reponse.json();

    const titres = donnees.map(article => article.title.toUpperCase());
    console.log("Titres transformés :");
    console.log(titres);
  } catch (erreur) {
    console.error("Une erreur est survenue :", erreur);
  }
}

traiterDonnees();

Dans cet exemple :

  • Nous récupérons une liste de données
  • Nous les convertissons en JSON
  • Nous les transformons
  • Nous les affichons

Chaque étape est claire et indépendante, ce qui rend le code facile à comprendre et à maintenir.


L’asynchronisme est au cœur de JavaScript et de node.js. C’est lui qui permet au langage de rester fluide, réactif et performant, que ce soit dans le navigateur ou côté serveur. Comprendre comment il fonctionne, c’est comprendre pourquoi JavaScript est conçu de cette manière et comment exploiter pleinement son potentiel.

Les callbacks ont ouvert la voie, les Promises ont apporté une structure, et async/await a finalement offert une syntaxe naturelle, lisible et efficace. Mais au-delà de la syntaxe, ce qui compte réellement, c’est la façon de penser. L’asynchrone n’est pas une astuce, mais une manière de voir le temps d’exécution : certaines actions prennent du temps, alors autant laisser le programme continuer plutôt que de l’arrêter.

Ce concept peut sembler abstrait au premier abord. Pourtant, avec de la pratique, vous vous surprendrez à l’utiliser instinctivement. Vous commencerez à comprendre quand l’utiliser, comment structurer vos fonctions, et comment éviter les pièges classiques. Et surtout, vous écrirez du code plus clair, plus agréable et plus solide.

La suite naturelle de cet apprentissage consiste à pratiquer. Reprenez vos anciens scripts. Essayez de les réécrire en Promises ou en async/await. Observez les différences. Sentez la lisibilité et la logique se renforcer.

Vous êtes maintenant prêt à aborder sereinement l’asynchronisme dans vos projets node.js et JavaScript, avec une nouvelle compréhension de ce qui se passe réellement derrière les lignes de code.