Qu’est-ce qu’un callback ?
Un callback est une fonction passée en argument à une autre fonction, qui sera exécutée plus tard (après un événement, une opération async, etc.).
Analogie : C’est comme donner votre numéro de téléphone en disant ‘Rappelle-moi quand tu as fini’.
Exemple :
function doSomething(callback) {
// Faire quelque chose
callback(); // Exécuter le callback
}
function myCallback() {
console.log('Callback exécuté!');
}
doSomething(myCallback); // Passer la fonction (sans ())Différence entre callback synchrone et asynchrone ?
Callback Synchrone :
- Exécuté immédiatement dans le même call stack
- Exemples : forEach, map, filter
[1, 2, 3].forEach(n => console.log(n)); // Sync
Callback Asynchrone :
- Exécuté plus tard, après une opération async
- Exemples : setTimeout, addEventListener, fetch
setTimeout(() => console.log('Async'), 0); // AsyncQu’est-ce que le callback hell ?
Le callback hell (pyramide de la mort) est une situation où les callbacks sont imbriqués profondément, rendant le code difficile à lire et maintenir.
Problème :
getData(a => {
getData2(a, b => {
getData3(b, c => {
getData4(c, d => {
getData5(d, e => {
console.log(e);
});
});
});
});
});Problèmes :
- Difficile à lire (pyramide)
- Gestion d’erreur complexe
- Difficile à débugger
- Code pas réutilisable
Quelles sont les solutions au callback hell ?
Solution 1 : Named Functions
function handleUser(err, user) {
if (err) return handleError(err);
getPostsByUser(user.id, handlePosts);
}
function handlePosts(err, posts) {
if (err) return handleError(err);
console.log(posts);
}
getUserById(userId, handleUser);Solution 2 : Promises
getUserById(userId) .then(user => getPostsByUser(user.id)) .then(posts => console.log(posts)) .catch(err => console.error(err));
Solution 3 : Async/Await
async function loadData() {
try {
const user = await getUserById(userId);
const posts = await getPostsByUser(user.id);
console.log(posts);
} catch (err) {
console.error(err);
}
}Qu’est-ce que le error-first callback pattern ?
Convention Node.js où le premier paramètre du callback est toujours l’erreur (null si succès).
Pattern :
function readFileAsync(filename, callback) {
// callback(error, result)
// Si erreur : callback(error, null)
// Si succès : callback(null, result)
}Usage :
fs.readFile('file.txt', (err, data) => {
if (err) {
console.error('Erreur:', err);
return; // Important : return pour arrêter
}
console.log('Données:', data);
});Avantages :
- Cohérence dans tout Node.js
- Force la gestion d’erreur
- Clair si erreur ou succès
Comment éviter de perdre le contexte ‘this’ dans un callback ?
Problème :
const obj = {
name: 'Alice',
greet() {
console.log(`Hello, ${this.name}`);
}
};
setTimeout(obj.greet, 1000); // 'Hello, undefined' ❌Solutions :
1. Arrow function (Recommandé) :
setTimeout(() => obj.greet(), 1000); // ✅
2. bind :
setTimeout(obj.greet.bind(obj), 1000); // ✅
3. Variable that :
const that = this;
setTimeout(function() {
that.greet();
}, 1000); // ✅Exemples de callbacks dans les Array Methods
forEach :
[1, 2, 3].forEach((num, index) => {
console.log(`Index ${index}: ${num}`);
});map :
const doubled = [1, 2, 3].map(n => n * 2); // [2, 4, 6]
filter :
const evens = [1, 2, 3, 4, 5, 6].filter(n => n % 2 === 0); // [2, 4, 6]
reduce :
const sum = [1, 2, 3, 4].reduce((acc, n) => acc + n, 0); // 10
Tous ces méthodes prennent une fonction callback.
Comment créer une fonction avec callback pour opération asynchrone ?
function delay(ms, callback) {
setTimeout(callback, ms);
}
// Usage
delay(1000, function() {
console.log('1 seconde écoulée');
});
// Avec données
function fetchUser(id, callback) {
setTimeout(function() {
const user = { id: id, name: 'Alice' };
callback(null, user); // Error-first
}, 1000);
}
// Usage
fetchUser(1, function(err, user) {
if (err) {
console.error(err);
return;
}
console.log('User:', user);
});Quel est le piège avec l’appel multiple d’un callback ?
Problème :
function getData(callback) {
callback('first');
callback('second'); // ❌ Appelé 2 fois !
}Un callback ne devrait être appelé qu’une seule fois.
Solution - Safe Callback :
function getData(callback) {
let called = false;
function safeCallback(...args) {
if (called) return;
called = true;
callback(...args);
}
// Utiliser safeCallback
safeCallback('first');
safeCallback('second'); // Ignoré
}Piège : callback parfois sync, parfois async
❌ TRÈS MAUVAIS :
function maybeSync(callback) {
if (cache) {
callback(cache); // Synchrone
} else {
fetchData((data) => {
callback(data); // Asynchrone
});
}
}Parfois sync, parfois async = comportement imprévisible.
✅ Toujours cohérent (async) :
function alwaysAsync(callback) {
if (cache) {
// Forcer async même si donnée en cache
process.nextTick(() => callback(cache));
// ou setTimeout(() => callback(cache), 0);
} else {
fetchData(callback);
}
}Créez une fonction retry avec callbacks
function retryOperation(operation, maxRetries, callback) {
let attempts = 0;
function attempt() {
attempts++;
operation((err, result) => {
if (err && attempts < maxRetries) {
console.log(`Tentative ${attempts} échouée, réessai...`);
attempt();
} else {
callback(err, result);
}
});
}
attempt();
}
// Usage
function unreliableOperation(callback) {
if (Math.random() < 0.7) {
callback(new Error('Échec aléatoire'));
} else {
callback(null, 'Succès!');
}
}
retryOperation(unreliableOperation, 5, (err, result) => {
if (err) {
console.error('Échec final:', err);
} else {
console.log('Résultat:', result);
}
});Créez une queue de callbacks
class CallbackQueue {
constructor() {
this.queue = [];
}
add(callback) {
this.queue.push(callback);
}
execute() {
this.queue.forEach(callback => callback());
this.queue = []; // Vider la queue
}
}
// Usage
const queue = new CallbackQueue();
queue.add(() => console.log('1'));
queue.add(() => console.log('2'));
queue.add(() => console.log('3'));
queue.execute(); // '1', '2', '3'