1.2Asynchrone : callbacks, Promises, async/await
L'essentiel en 30 secondes
- L'asynchrone = « quand ce sera prêt » : les opérations lentes se lancent et leur résultat arrive plus tard — on ne peut pas le lire sur la ligne suivante.
- Callbacks → callback hell : la convention error-first (erreur en 1er argument) fonctionne, mais les callbacks dépendants s'imbriquent en pyramide illisible.
- async/await à privilégier : await attend sans bloquer, le code se lit comme du synchrone, erreurs via try/catch — construit sur les Promises.
Mini-cas filé — Salim attend ses fichiers à Alger
Salim doit lire un fichier de configuration, puis interroger une base avec son contenu, puis appeler une API : il écrit trois callbacks imbriqués et se perd dans l'indentation — pire, son console.log affiche undefined car il lit le résultat avant qu'il arrive. Il réécrit le tout en async/await avec try/catch : trois lignes await lisibles dans l'ordre, et l'erreur gérée à un seul endroit. Le bug de l'undefined disparaît avec l'ancien modèle mental.
Suite du cas 1.1 — Salim passe du langage au cœur de Node : l'asynchrone.
1. Des callbacks au callback hell
L'asynchrone est LE concept central de Node : les opérations lentes (lire un fichier, interroger une base, appeler une API) se lancent et leur résultat arrive plus tard. Le mécanisme historique est le callback : une fonction passée en argument, rappelée à la fin de l'opération — avec la convention error-first (erreur en premier argument, null si tout va bien). Enchaîner des opérations dépendantes mène au callback hell : une pyramide illisible, difficile à gérer surtout pour les erreurs.
Salim écrit fs.readFile('config.txt', (err, data) => ...) et repère la convention error-first : tester err d'abord, exploiter data ensuite — jamais l'inverse.
2. Les Promises : la valeur future chaînée
Une Promise représente une valeur future — le résultat d'une opération pas encore terminée. Trois états : pending (en attente), fulfilled (succès, une valeur), rejected (erreur). On réagit avec .then() (succès) et .catch() (erreur), et les Promises se chaînent au lieu de s'imbriquer — bien plus lisible que le callback hell.
Salim réécrit sa lecture de fichier en fs.promises.readFile(...).then().catch() : la chaîne remplace la pyramide et l'erreur atterrit dans un seul .catch().
3. async/await : la lisibilité (à privilégier)
La vraie révolution de lisibilité est async/await : dans une fonction async, await attend le résultat sans bloquer (sucre syntaxique sur les Promises) et le code asynchrone se lit comme du synchrone, ligne après ligne, avec un try/catch classique pour les erreurs. C'est le style dominant et recommandé : vous rencontrerez les trois formes dans le code existant, mais vous écrirez principalement en async/await.
Salim termine avec const data = await fs.promises.readFile(...) dans un try/catch : trois opérations en trois lignes ordonnées — son code asynchrone se lit enfin comme une recette.
La pyramide infernale (ne faites jamais ça)
Moralité : imbriquer, c'est s'enterrer — aplatir avec await, c'est respirer.
🔎 Exemple concret — 🔎 Exemple concret — Le même code en async/await
Lisible, non-bloquant, erreurs gérées — le style à écrire.
const fs = require("fs");
async function lire() {
try {
const data = await fs.promises.readFile("f.txt", "utf8");
console.log(data);
} catch (err) {
console.error("Echec :", err.message);
}
}
lire();
Lecture : await attend le contenu sans bloquer ; en cas d'échec, le catch affiche le message.
Leçon : Promise.all pour lancer plusieurs opérations indépendantes en parallèle.
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.
Exemple simple : Le (err, data) => ... de la lecture de fichier de Salim.
Ce que ce n'est pas : une fonction appelée immédiatement avec un résultat déjà prêt.
Réflexe : tester err d'abord, exploiter le résultat ensuite.
- Callback hell
- Imbrication illisible de callbacks quand on enchaîne des opérations asynchrones dépendantes ; problème résolu par les Promises et async/await.
Exemple simple : La pyramide à trois niveaux du premier script de Salim.
Ce que ce n'est pas : trois await alignés lisiblement dans une fonction async.
Réflexe : dès que ça s'imbrique, passer aux Promises.
- Promise
- Objet représentant une valeur future. Trois états : pending, fulfilled (succès), rejected (erreur). Réactions via .then() et .catch().
Exemple simple : La promesse renvoyée par fs.promises.readFile chez Salim.
Ce que ce n'est pas : le contenu du fichier disponible sur la ligne suivante.
Réflexe : .then pour le succès, .catch pour l'erreur.
- async/await
- Syntaxe moderne construite sur les Promises : await attend le résultat sans bloquer ; le code asynchrone se lit comme du synchrone. Erreurs via try/catch.
Exemple simple : Les trois await ordonnés du script final de Salim.
Ce que ce n'est pas : un sleep qui bloquerait toute l'application.
Réflexe : écrire en async/await par défaut.
- error-first / try-catch
- Gestion d'erreurs selon la forme : erreur en premier argument des callbacks, .catch() pour les Promises, try/catch autour des await.
Exemple simple : Le try/catch unique qui sécurise les trois opérations de Salim.
Ce que ce n'est pas : ignorer l'erreur et laisser l'application planter en silence.
Réflexe : toujours un chemin d'erreur explicite.
En pratique — En pratique — Maîtriser l'asynchrone
- Écrivez une opération asynchrone avec un callback (ex. fs.readFile) et repérez la convention error-first (err, data).
- Enchaînez deux opérations dépendantes avec des callbacks imbriqués : constatez le début du callback hell.
- Réécrivez avec des Promises et le chaînage .then().then().catch() : observez la meilleure lisibilité.
- 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.
Script prêt à dire — expliquer l'asynchrone à un collègue (1 min)
« En Node, une opération lente se lance mais son résultat arrive plus tard : on ne le lit jamais sur la ligne suivante. On dit +quand ce sera prêt, fais ceci+ — avec un callback, un .then() ou un await. C'est pour ça qu'on écrit en async/await : le code attend sans bloquer et se lit dans l'ordre. »
À dire devant le bug classique du console.log qui affiche undefined.
Modèle copier-coller — lecture de fichier async/await
const fs = require("fs");
async function lire(nom) {
try {
const data = await fs.promises.readFile(nom, "utf8");
console.log(data);
return data;
} catch (err) {
console.error("Echec :", err.message);
return null;
}
}
lire("f.txt");
Opérations dépendantes
Lire un fichier, puis interroger avec son contenu : enchaîner les await dans l'ordre dans un seul try/catch — comme Salim avec config → base → API.
Opérations indépendantes
Plusieurs lectures sans lien entre elles : les lancer ensemble avec Promise.all pour aller vite au lieu d'attendre chacune son tour.
Vieux code en callbacks
Face à une API error-first existante : la comprendre et l'utiliser telle quelle pour lire, mais écrire tout le code neuf en async/await.
Erreur silencieuse à Alger
Salim ajoute systématiquement un catch qui affiche err.message : une erreur asynchrone non gérée fait planter ou ment en silence — jamais d'await sans filet.
✓ Bonnes pratiques
- Distinguer « lancer » et « obtenir ».
- Écrire en async/await par défaut.
- Toujours un chemin d'erreur explicite.
✗ Pièges fréquents
- Lire le résultat ligne suivante.
- Imbriquer les callbacks dépendants.
- await sans try/catch.
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.
- Progression : CALLBACKS (error-first) → risque de callback hell → 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 (à privilégier) : await attend sans bloquer, le code se LIT comme du synchrone, erreurs via try/catch.
Défi 48h — appliquez demain
Prenez le modèle ci-dessus : lisez un vrai fichier avec un callback, réécrivez en Promises, puis en async/await avec try/catch. Comparez les trois versions. 25 minutes suffisent.
- Je peux expliquer « lancer » vs « obtenir le résultat ».
- Je sais quand utiliser callback, Promise ou await.
- J'ai écrit la même opération sous les trois formes.
Questions fréquentes
Pourquoi l'asynchrone est-il si déroutant au début, et comment vraiment le comprendre ?
Il rompt avec l'intuition « chaque ligne se termine avant la suivante ». Le piège classique : lire le résultat sur la ligne suivant l'appel — on obtient undefined car l'opération n'a pas encore fini. Le modèle mental à adopter : distinguer « lancer » (immédiat) et « obtenir le résultat » (plus tard, via callback, .then() ou await). Pensez « quand ce sera prêt, fais ceci » — comme une commande au restaurant : on ne reste pas planté à la cuisine, on continue et on est servi quand c'est prêt. Une fois ce déclic passé, comme pour Salim, tout Node devient lisible.
Callbacks, Promises ou async/await : lequel utiliser aujourd'hui ?
Vous devez connaître les trois mais écrire en async/await. Callbacks : la forme historique, à comprendre pour lire le code existant et les API classiques (convention error-first) — évitez d'en imbriquer. Promises : le socle moderne (async/await est construit dessus) — à comprendre, et utiles directement pour le parallélisme (Promise.all). async/await : votre style par défaut pour tout code neuf — lisible, erreurs via try/catch. Règle pratique : écrire en await, comprendre les Promises, savoir lire les callbacks.
Que fait await dans une fonction async ?