1.2 · Asynchrone : callbacks, Promises, async/await

Niveau 1 · JavaScript & bases Node

1.2Asynchrone : callbacks, Promises, async/await

Objectif : comprendre le cœur asynchrone de Node et éviter le « callback hell ».
Temps estimé : 13 min

L'asynchrone est LE concept central de Node — celui qui, une fois maîtrisé, débloque tout le reste, et qui, mal compris, cause le plus de confusion chez les débutants. Rappel (section 0-1) : Node est non-bloquant ; les opérations lentes (lire un fichier, interroger une base, appeler une API) ne s'exécutent pas « sur place » en bloquant le programme — elles se lancent, et leur résultat arrive PLUS TARD. Il faut donc un mécanisme pour dire « quand ce sera prêt, fais ceci ». Historiquement, ce mécanisme fut le callback : une fonction qu'on passe en argument et que Node appellera quand l'opération est terminée. Exemple classique : fs.readFile('f.txt', (err, data) => { ... }) — on passe une fonction qui recevra soit une erreur (err), soit les données (data) quand la lecture est finie. La convention Node est le « error-first callback » : le premier argument est l'erreur (null si tout va bien), les suivants le résultat.

Les callbacks fonctionnent mais mènent vite au « callback hell » (l'enfer des callbacks) : quand on enchaîne plusieurs opérations asynchrones dépendantes (lire un fichier, puis interroger une base avec le résultat, puis appeler une API…), les callbacks s'imbriquent en une pyramide illisible et difficile à gérer (surtout pour les erreurs). Pour résoudre cela, JavaScript a introduit les Promises (promesses). Une Promise représente une valeur future — le résultat d'une opération asynchrone pas encore terminée. Une Promise est dans l'un de trois états : pending (en attente), fulfilled (résolue avec succès, une valeur), ou rejected (rejetée avec une erreur). On réagit avec .then(résultat => ...) (en cas de succès) et .catch(err => ...) (en cas d'erreur). Les Promises se chaînent (.then().then().then()) au lieu de s'imbriquer — bien plus lisible. Mais la vraie révolution de lisibilité est async/await (moderne, à privilégier). En marquant une fonction async, on peut y utiliser await devant une opération asynchrone : le code « attend » le résultat et continue, mais SANS bloquer (c'est du sucre syntaxique sur les Promises). Résultat : du code asynchrone qui se LIT comme du code synchrone, ligne après ligne — const data = await fs.promises.readFile('f.txt');. La gestion d'erreurs se fait alors avec un try/catch classique. async/await est aujourd'hui le style DOMINANT et recommandé pour l'asynchrone en Node : lisible, chaînable naturellement, avec une gestion d'erreurs claire. Comprendre cette progression — callbacks (l'origine) → Promises (le chaînage) → async/await (la lisibilité) — est essentiel : vous rencontrerez les trois dans du code existant, mais vous écrirez principalement en async/await. Maîtriser l'asynchrone est LE cap à passer pour être à l'aise en Node ; c'est exigeant au début, mais tout le reste en dépend.

Vocabulaire de la section

Callback
Fonction passée en argument, appelée quand une opération asynchrone se termine. Convention Node « error-first » : premier argument = erreur (null si OK), puis le résultat.
Callback hell
Imbrication illisible de callbacks quand on enchaîne des opérations asynchrones dépendantes ; problème que les Promises et async/await résolvent.
Promise
Objet représentant une valeur future (résultat d'une opération asynchrone). Trois états : pending, fulfilled (succès), rejected (erreur). Réactions via .then() et .catch().
async/await
Syntaxe moderne (sucre sur les Promises) : `await` attend le résultat d'une opération asynchrone sans bloquer ; le code asynchrone se lit comme du synchrone. Erreurs via try/catch.
error-first / try-catch
Gestion d'erreurs : erreur en premier argument des callbacks ; .catch() pour les Promises ; try/catch autour des await.
Vérifiez votre compréhension

Quel est le style moderne recommandé pour gérer l'asynchrone ?

Tutoriel 1.2
Tutos « 1.2 » JavaScript asynchrone callback Promise async await (recherche)
Cliquer pour voir les résultats à jour ↗

En pratique — Maîtriser l'asynchrone

  1. Écrivez une opération asynchrone avec un callback (ex. fs.readFile) et repérez la convention error-first (err, data).
  2. Enchaînez deux opérations dépendantes avec des callbacks imbriqués : constatez le début du « callback hell ».
  3. Réécrivez avec des Promises et le chaînage `.then().then().catch()` : observez la meilleure lisibilité.
  4. Réécrivez enfin en async/await (fonction `async`, `await` devant les opérations, `try/catch` pour les erreurs) : le code se lit comme du synchrone.
Vous comprenez et manipulez l'asynchrone sous ses trois formes (callbacks, Promises, async/await), et privilégiez async/await pour un code lisible — le cap central de Node.

Points clés à retenir

  • L'ASYNCHRONE est LE concept central de Node : les opérations lentes se lancent et leur résultat arrive PLUS TARD — il faut dire « quand c'est prêt, fais ceci ».
  • Progression historique : CALLBACKS (error-first : err en 1er argument) → risque de « callback hell » (imbrication illisible) → PROMISES (chaînage .then/.catch) → ASYNC/AWAIT.
  • Une PROMISE = valeur future (pending/fulfilled/rejected) ; on réagit avec .then() (succès) et .catch() (erreur).
  • ASYNC/AWAIT (moderne, à privilégier) : `await` attend sans bloquer, le code asynchrone se LIT comme du synchrone, erreurs via try/catch. Maîtriser l'asynchrone = le cap de Node.

Questions fréquentes

Pourquoi l'asynchrone est-il si déroutant au début, et comment vraiment le comprendre ?

L'asynchrone déroute parce qu'il rompt avec l'intuition de « le code s'exécute ligne après ligne, dans l'ordre » — et le comprendre demande un changement de modèle mental, mais c'est LE cap qui fait passer de débutant à développeur Node à l'aise. La source de confusion : en programmation « synchrone » classique (ce à quoi on est habitué), chaque ligne s'exécute et se TERMINE avant la suivante. Ligne 1, puis ligne 2, puis ligne 3 — dans l'ordre, chacune finie avant la suivante. En asynchrone, ce n'est PAS le cas : une opération asynchrone se LANCE, mais son résultat arrive PLUS TARD, et le code CONTINUE sans attendre. Le piège classique du débutant : const data = fs.readFile('f.txt'); console.log(data); (en version callback) — on s'attend à voir le contenu du fichier, mais on voit undefined, parce que readFile n'a pas encore fini quand console.log s'exécute ! Le résultat n'est pas « prêt » à la ligne suivante. C'est déroutant car cela contredit l'ordre apparent du code. Comment vraiment comprendre — le modèle mental à adopter : (1) Distinguez « lancer » et « obtenir le résultat ». Une opération asynchrone se lance immédiatement, mais son RÉSULTAT arrive dans le futur. Vous ne pouvez pas « attendre bêtement » sur la ligne suivante — il faut dire « QUAND le résultat arrivera, fais ceci ». (2) Pensez en termes de « quand ce sera prêt ». Un callback, un .then(), un await disent tous : « voici quoi faire QUAND l'opération sera terminée ». Le code après une opération asynchrone ne s'exécute pas « après » dans le temps, mais « quand le résultat arrive ». (3) L'analogie du restaurant / de la commande. Vous commandez un plat (lancez l'opération), mais vous ne restez pas planté à la cuisine à attendre (ce serait bloquant) — vous continuez à faire autre chose, et quand le plat est prêt, on vous le sert (le callback/then/await s'exécute). Le « quand c'est prêt » est la clé. (4) async/await aide énormément. La bonne nouvelle : async/await rend l'asynchrone BEAUCOUP plus intuitif. Avec const data = await fs.promises.readFile('f.txt');, le mot await dit « attends que ce soit prêt, PUIS continue » — et le code se lit alors comme du synchrone (data contient bien le résultat à la ligne suivante). await « masque » la complexité asynchrone en donnant l'apparence du séquentiel, tout en restant non-bloquant sous le capot. C'est pourquoi on recommande d'apprendre et d'utiliser async/await : il rend l'asynchrone lisible et proche de l'intuition. Conseils pour franchir le cap : (1) Commencez par comprendre le PROBLÈME (le résultat n'est pas prêt à la ligne suivante) avant les solutions. (2) Privilégiez async/await dès que possible (plus intuitif que les callbacks). (3) Pratiquez avec de vraies opérations asynchrones (lire un fichier, un timer setTimeout, une requête) et observez l'ordre réel d'exécution (ajoutez des console.log pour voir quoi s'exécute quand — révélateur). (4) Acceptez que ça prenne du temps : l'asynchrone est un concept qui « clique » après plusieurs confrontations — c'est normal de galérer au début, tout le monde passe par là. (5) Ne mélangez pas les styles au hasard (callbacks ET promises ET await dans tous les sens) — tenez-vous à async/await pour la clarté. Une fois le cap passé (le modèle « lancer / quand c'est prêt » intégré, et async/await maîtrisé), l'asynchrone devient naturel, et vous débloquez TOUT Node (l'I/O, les serveurs, les bases de données reposent dessus). C'est l'investissement le plus rentable de votre apprentissage Node — persévérez, ça vaut le coup.

Callbacks, Promises ou async/await : lequel utiliser aujourd'hui, et faut-il connaître les trois ?

Vous devez CONNAÎTRE les trois (car ils coexistent dans le code réel) mais ÉCRIRE principalement en async/await (le style moderne et recommandé) — voici la répartition claire. Ce que vous devez SAVOIR sur chacun : (1) Callbacks — l'origine, à comprendre : c'est la forme historique de l'asynchrone en Node, et beaucoup d'API Node « classiques » et de bibliothèques anciennes l'utilisent encore (fs.readFile(path, callback)). Vous DEVEZ comprendre les callbacks et la convention error-first (erreur en premier argument) pour LIRE et utiliser ce code. Mais évitez d'en imbriquer beaucoup (le callback hell) dans votre propre code — préférez les convertir en Promises/await. (2) Promises — le socle moderne, à comprendre : les Promises sont le fondement de l'asynchrone moderne. async/await est d'ailleurs construit DESSUS (await attend une Promise). Vous devez comprendre les Promises (états, .then/.catch, chaînage) car : elles sont partout (les API modernes renvoient des Promises), async/await les manipule sous le capot, et certaines situations les utilisent directement (par exemple Promise.all() pour lancer plusieurs opérations en parallèle — très utile). Vous écrirez parfois du .then()/.catch(), mais surtout vous les « consommerez » via await. (3) async/await — ce que vous ÉCRIVEZ principalement : c'est le style moderne, recommandé, le plus lisible. Pour votre propre code, écrivez en async/await par défaut. La règle pratique : (1) Écrivez en async/await — c'est votre style par défaut pour tout code asynchrone que vous produisez. Lisible (se lit comme du synchrone), gestion d'erreurs claire (try/catch), pas de callback hell. (2) Comprenez les Promises — car async/await repose dessus, et vous les utiliserez directement dans certains cas (notamment Promise.all pour le parallélisme : lancer plusieurs opérations asynchrones en même temps et attendre qu'elles finissent toutes — un pattern important pour la performance). (3) Sachez lire les callbacks — car du code existant et des API Node classiques les utilisent ; sachez aussi les convertir en Promises (Node fournit util.promisify, et beaucoup d'API ont des versions Promise, comme fs.promises). Cas particuliers utiles à connaître : (1) Promise.all([...]) : lancer plusieurs opérations asynchrones EN PARALLÈLE et attendre toutes les résultats — bien plus rapide que de les faire une par une avec await successifs quand elles sont indépendantes. Exemple : récupérer 3 ressources indépendantes en même temps plutôt qu'en séquence. C'est un pattern de performance important — on l'utilise avec await : const [a, b, c] = await Promise.all([f1(), f2(), f3()]);. (2) La gestion d'erreurs : avec async/await, entourez vos await d'un try/catch ; n'oubliez pas de gérer les erreurs (une Promise rejetée non gérée est un bug fréquent). (3) Évitez de MÉLANGER les styles de façon désordonnée dans un même code (callbacks + then + await partout) — restez cohérent (async/await) pour la lisibilité. En résumé : le paysage a évolué (callbacks → Promises → async/await), et aujourd'hui async/await est le standard que vous écrivez, construit sur les Promises que vous comprenez, tandis que les callbacks restent à connaître pour le code existant. Concentrez votre écriture sur async/await (moderne, lisible), maîtrisez les Promises (le socle, avec Promise.all pour le parallélisme), et sachez lire/convertir les callbacks. Cette combinaison couvre tout ce que vous rencontrerez. Ne vous laissez pas intimider par la coexistence des trois : c'est l'histoire du langage, et async/await est l'aboutissement que vous privilégierez naturellement.

Autres ressources