L'Asynchrone en Node.js

Callbacks & Promises — .then() et .catch()

Utilisez les flèches, cliquez ou glissez pour naviguer

Objectifs de la leçon

1. Pourquoi l'asynchrone est indispensable

Node.js single-threaded mais non-bloquant

2. Le pattern callback et ses limites

Convention (error, result) et callback hell

3. Créer et utiliser des Promises

.then() et .catch()

4. Chaîner les Promises

Code plat au lieu du triangle de la mort

5. Différence entre code synchrone et asynchrone

L'ordre d'exécution change tout

Plan du cours

1

Pourquoi l'asynchrone

Analogie du serveur de restaurant — ne jamais rester planté à attendre

2

Sync vs Async

Montrer la différence avec setTimeout — l'ordre d'exécution change

3

Les Callbacks

La première solution, convention (error, result), puis le callback hell

4

Les Promises

pending, fulfilled, rejected — .then() et .catch()

5

Chaîner les Promises

Code plat au lieu du triangle de la mort

Pourquoi l'asynchrone?

L'analogie du serveur de restaurant

🧑‍🍳❌

Synchrone

Le serveur attend que chaque table ait fini avant de passer à la suivante

🧑‍🍳✅

Asynchrone

Le serveur prend la commande, passe à la table suivante, revient quand c'est prêt

Node.js : Single-threaded mais non-bloquant

Node.js n'a qu'un seul thread d'exécution

Mais il délègue les opérations lentes au système

🐢

Lecture fichier

10-100ms

🌐

Requête HTTP

50-500ms

🗄️

Query BDD

5-200ms

💡 Si Node.js attendait de façon synchrone, tous les utilisateurs seraient bloqués!

L'Event Loop : le cœur de Node.js

1️⃣

Le code synchrone s'exécute en premier

2️⃣

Les opérations lentes sont déléguées (OS, libuv)

3️⃣

L'Event Loop vérifie en boucle si une tâche est terminée

4️⃣

Quand c'est prêt → le callback est exécuté

🎯 Un seul thread, mais des milliers de requêtes simultanées grâce à l'Event Loop

Code synchrone : tout dans l'ordre

// Code synchrone

console.log('Étape 1');

console.log('Étape 2');

console.log('Étape 3');

// Résultat :

Étape 1

Étape 2

Étape 3

✅ Prévisible — chaque ligne attend la fin de la précédente

Code asynchrone : l'ordre surprend!

console.log('Étape 1');

// ⏱ Tâche asynchrone — 0ms!

setTimeout(() => {

console.log('Étape 2 (dans setTimeout)');

}, 0);

console.log('Étape 3');

// Résultat :

Étape 1

Étape 3

Étape 2 (dans setTimeout)

⚠️ Même avec 0ms, le callback s'exécute APRÈS le code synchrone!

Sync vs Async : comparaison

❌ Synchrone

Lire fichier... ⏳ 50ms
Écrire réponse... ⏳ bloqué
Traiter suivant... ⏳ attend

Total : 150ms pour 3 requêtes

✅ Asynchrone

Lancer lecture fichier
Lancer 2e requête
Lancer 3e requête
Callbacks arrivent...

Total : ~50ms pour 3 requêtes!

Callback : le principe

Passer une fonction qui sera appelée plus tard, quand l'opération sera finie

// Lecture asynchrone d'un fichier

const fs = require('fs');

fs.readFile('hello.txt', 'utf8', callback);

function callback(err, data) {

if (err) throw err;

console.log(data);

}

La convention Node.js : (error, result)

// 1er argument = erreur (null si OK)

// 2e argument = résultat

fs.readFile('fichier.txt', 'utf8', (err, data) => {

if (err) {

console.error('Erreur:', err);

return;

}

console.log(data);

});

💡 Error en premier pour ne jamais oublier de gérer les erreurs!

⚠️ Le Callback Hell

Le triangle de la mort

Quand on enchaîne les callbacks...

Le code s'enfonce vers la droite

⟩⟩⟩⟩⟩⟩⟩⟩

Le Callback Hell en action

fs.readFile('user.json', (err, user) => {

if (err) throw err;

db.find(user.id, (err, profile) => {

if (err) throw err;

api.get(profile.url, (err, data) => {

if (err) throw err;

fs.writeFile('result.json', data, (err) => {

if (err) throw err;

// 😱 4 niveaux de profondeur!

console.log('Terminé!');

});

});

});

});

❌ Illisible, impossible à maintenir, erreurs non gérées proprement

Pourquoi le callback hell est un problème

❌ Code illisible

L'indentation s'enfonce vers la droite

❌ Gestion des erreurs répétée

Chaque callback doit vérifier err

❌ Impossible de retourner une valeur

Pas de return depuis un callback

✅ Solution : les Promises!

Les Promises

Une valeur qui arrivera plus tard

Pending

En attente

Fulfilled

Résolue ✅

Rejected

Rejetée ❌

Créer une Promise

const maPromise = new Promise((resolve, reject) => {

if (success) {

resolve(resultat);

// ✅ Promise → Fulfilled

} else {

reject(erreur);

// ❌ Promise → Rejected

}

});

resolve(valeur)

Succès — la Promise est remplie

reject(erreur)

Échec — la Promise est rejetée

Exemple : Promise pour lire un fichier

const lireFichier = new Promise((resolve, reject) => {

fs.readFile('data.json', 'utf8', (err, data) => {

if (err) {

reject(err);

} else {

resolve(data);

}

});

});

💡 La Promise "enveloppe" le callback — on passe d'un style callback à un style Promise

Utiliser une Promise : .then() et .catch()

lireFichier

.then((data) => {

console.log('Contenu:', data);

})

.catch((err) => {

console.error('Erreur:', err);

});

.then()

Exécuté si la Promise est résolue

Reçoit la valeur de resolve()

.catch()

Exécuté si la Promise est rejetée

Reçoit l'erreur de reject()

⚠️ Toujours ajouter .catch() — ne jamais ignorer les erreurs!

Cycle de vie d'une Promise

Pending

En attente

Fulfilled

→ .then()

Rejected

→ .catch()

🎯 Une Promise passe de Pending à Fulfilled ou Rejected — jamais en arrière. C'est irréversible!

Ne pas confondre : Création vs Utilisation

🏗️ Création

new Promise()

const p = new Promise((resolve, reject) => {

// logique asynchrone

});

On définit quand resolve/reject sont appelés

📱 Utilisation

.then() / .catch()

p.then((val) => {

// utiliser la valeur

}).catch((err) => {

// gérer l'erreur

});

On réagit au résultat

⚠️ Piège : créer des Promises quand on devrait juste les utiliser (avec .then/.catch)

Chaîner les Promises

Code plat au lieu du triangle de la mort

Callback Hell

⟩⟩⟩⟩⟩⟩⟩⟩

Enfoncé vers la droite

Promise Chain

→→→→→→→→

Plat et lisible!

Le chaînage : .then().then().catch()

lireFichier('user.json')

.then((user) => chercherProfile(user.id))

.then((profile) => telechargerPhoto(profile.photoUrl))

.then((photo) => afficherPhoto(photo))

.catch((err) => {

console.error('Erreur:', err);

});

✅ Code plat et lisible — un seul .catch() pour toutes les erreurs!

Comment fonctionne le chaînage

1️⃣

.then() retourne une nouvelle Promise

2️⃣

La valeur retournée par le callback devient la valeur de cette Promise

3️⃣

Le .then() suivant attend cette nouvelle Promise

4️⃣

Si une erreur se produit → saute directement au .catch()

Promise.resolve(1)

.then(val => val + 1) // 2

.then(val => val * 3) // 6

.then(val => console.log(val)) // 6

Avant / Après : même logique, code différent

❌ Callback Hell

readFile('a', (err, a) => {

readFile('b', (err, b) => {

readFile('c', (err, c) => {

// 😱 3 niveaux

});

});

});

✅ Promise Chain

readFileP('a')

.then(a => readFileP('b'))

.then(b => readFileP('c'))

.then(c => /* plat! */)

.catch(err => /* 1 seul catch */);

🎯 Le chaînage résout les 3 problèmes : lisibilité, erreurs centralisées, pas d'indentation profonde

⚠️ Piège 1 : L'ordre d'exécution asynchrone

❌ L'erreur

let data;

fetch('/api')

.then(res => data = res);

console.log(data); // undefined!

console.log s'exécute AVANT le .then()

✅ La solution

fetch('/api')

.then(res => {

console.log(res);

});

Utiliser les données DANS le .then()

💡 Règle d'or : tout ce qui dépend du résultat asynchrone doit être dans .then()

⚠️ Piège 2 : Oublier de gérer les erreurs

❌ Sans .catch()

fetch('/api')

.then(res => res.json());

// Si erreur → silence total!

// UnhandledPromiseRejection

✅ Avec .catch()

fetch('/api')

.then(res => res.json())

.catch(err => {

console.error('Erreur:', err);

});

⚠️ Une Promise rejetée sans .catch() peut crasher le process Node.js!

⚠️ Piège 3 : Confondre new Promise et .then/.catch

❌ Créer une Promise inutile

const p = new Promise((resolve) => {

fetch('/api').then(resolve);

});

// 😱 Inutile!

✅ Utiliser directement la Promise

fetch('/api')

.then(res => res.json())

.catch(err => ...);

// 😊 Simple!

💡 new Promise() est nécessaire seulement quand on "enveloppe" un callback. Si la fonction retourne déjà une Promise, utilisez-la directement!

Points clés à retenir

Node.js est single-threaded mais non-bloquant

Il délègue les opérations lentes via l'Event Loop

Le callback hell rend le code illisible

Les Promises résolvent ce problème

Promise = valeur future

resolve() pour le succès, reject() pour l'erreur

Le chaînage .then().then().catch() garde le code plat et lisible

Un seul .catch() pour toutes les erreurs

Récapitulatif

Callbacks

Première solution

→ Convention (err, result)

→ Callback hell 😱

Promises

Valeur future

→ .then() / .catch()

→ Chaînage plat ✅

Pending

En attente

→ Pas encore résolue

Fulfilled / Rejected

Résolue ✅ ou Rejetée ❌

→ Irréversible

⚠️ Toujours ajouter .catch()!

L'asynchrone surprend toujours — testez avec console.log avant/après

Exercices pratiques

1. Prédire l'ordre d'exécution

Écrire un code avec setTimeout et console.log — deviner l'ordre avant de tester

2. Créer une Promise qui résout après un délai

Utiliser setTimeout dans new Promise, puis .then() pour afficher le résultat

3. Chaîner 3 Promises

Lire un fichier → parser le JSON → afficher un champ — avec .catch()

4. Convertir un callback en Promise

Prendre fs.readFile (callback) et l'envelopper dans new Promise

Questions?

L'asynchrone est le cœur de Node.js

Prochaine étape : async/await

Encore plus lisible que .then()!