Callbacks & Promises — .then() et .catch()
Utilisez les flèches, cliquez ou glissez pour naviguer
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
Pourquoi l'asynchrone
Analogie du serveur de restaurant — ne jamais rester planté à attendre
Sync vs Async
Montrer la différence avec setTimeout — l'ordre d'exécution change
Les Callbacks
La première solution, convention (error, result), puis le callback hell
Les Promises
pending, fulfilled, rejected — .then() et .catch()
Chaîner les Promises
Code plat au lieu du triangle de la mort
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 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!
Le code synchrone s'exécute en premier
Les opérations lentes sont déléguées (OS, libuv)
L'Event Loop vérifie en boucle si une tâche est terminée
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
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
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!
❌ Synchrone
Total : 150ms pour 3 requêtes
✅ Asynchrone
Total : ~50ms pour 3 requêtes!
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);
}
// 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 triangle de la mort
Quand on enchaîne les callbacks...
Le code s'enfonce vers la droite
⟩⟩⟩⟩⟩⟩⟩⟩
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
❌ 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!
Une valeur qui arrivera plus tard
Pending
En attente
Fulfilled
Résolue ✅
Rejected
Rejetée ❌
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
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
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!
⏳
Pending
En attente
→
✅
Fulfilled
→ .then()
❌
Rejected
→ .catch()
🎯 Une Promise passe de Pending à Fulfilled ou Rejected — jamais en arrière. C'est irréversible!
🏗️ 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)
Code plat au lieu du triangle de la mort
Callback Hell
⟩⟩⟩⟩⟩⟩⟩⟩
Enfoncé vers la droite
Promise Chain
→→→→→→→→
Plat et lisible!
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!
.then() retourne une nouvelle Promise
La valeur retournée par le callback devient la valeur de cette Promise
Le .then() suivant attend cette nouvelle Promise
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
❌ 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
❌ 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()
❌ 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!
❌ 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!
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
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
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
L'asynchrone est le cœur de Node.js
Prochaine étape : async/await
Encore plus lisible que .then()!