Callbacks and Promise Chaining
Understanding asynchronous callback patterns
Challenge
Section titled “Challenge”Avoid callback hell, properly chain asynchronous operations, and handle errors across chains.
Explanation
Section titled “Explanation”Callbacks are the foundation of asynchronous JavaScript. Modern code prefers Promises and async/await, but callbacks remain important.
Callback Basics
Section titled “Callback Basics”// Synchronous callbackfunction forEach(array, callback) { for (let i = 0; i < array.length; i++) { callback(array[i], i); }}
forEach([1, 2, 3], (item, index) => { console.log(`${index}: ${item}`);});
// Asynchronous callbackfunction fetchUser(id, callback) { setTimeout(() => { const user = { id, name: 'John' }; callback(user); }, 1000);}
fetchUser(1, (user) => { console.log('User:', user);});Callback Hell
Section titled “Callback Hell”// ❌ Callback hell (pyramid of doom)fetchUser(1, (user) => { fetchPosts(user.id, (posts) => { fetchComments(posts[0].id, (comments) => { displayComments(comments); }); });});
// ✅ Named functionsfunction handleUser(user) { fetchPosts(user.id, handlePosts);}
function handlePosts(posts) { fetchComments(posts[0].id, handleComments);}
function handleComments(comments) { displayComments(comments);}
fetchUser(1, handleUser);
// ✅ Promises (better)fetchUserPromise(1) .then(user => fetchPostsPromise(user.id)) .then(posts => fetchCommentsPromise(posts[0].id)) .then(comments => displayComments(comments));Error Handling with Callbacks
Section titled “Error Handling with Callbacks”// Error-first callback conventionfunction readFile(path, callback) { fs.readFile(path, (error, data) => { if (error) { callback(error); // Pass error as first arg } else { callback(null, data); // null for no error } });}
readFile('file.txt', (error, data) => { if (error) { console.error('Failed to read:', error); } else { console.log('File content:', data); }});
// Multiple operations with error handlingfunction processFile(path, callback) { readFile(path, (error, data) => { if (error) return callback(error);
parseData(data, (error, parsed) => { if (error) return callback(error);
saveData(parsed, (error) => { if (error) return callback(error); callback(null, 'Success'); }); }); });}Promisifying Callbacks
Section titled “Promisifying Callbacks”// Convert callback to Promisefunction promisify(fn) { return (...args) => { return new Promise((resolve, reject) => { fn(...args, (error, result) => { if (error) reject(error); else resolve(result); }); }); };}
const readFileAsync = promisify(fs.readFile);
// Now use as PromisereadFileAsync('file.txt', 'utf8') .then(data => console.log(data)) .catch(error => console.error(error));
// Built-in promisify (Node.js)const { promisify } = require('util');const readFile = promisify(fs.readFile);Callback Queuing
Section titled “Callback Queuing”// Queue callbacks and run sequentiallyclass Queue { constructor() { this.callbacks = []; }
add(callback) { this.callbacks.push(callback); return this; }
run() { if (this.callbacks.length === 0) return;
const callback = this.callbacks.shift(); callback(() => this.run()); }}
const queue = new Queue();queue .add((next) => { setTimeout(() => { console.log('Task 1'); next(); }, 1000); }) .add((next) => { console.log('Task 2'); next(); }) .run();Callback Patterns
Section titled “Callback Patterns”// Observer pattern with callbacksclass EventEmitter { constructor() { this.events = {}; }
on(event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); }
emit(event, data) { if (this.events[event]) { this.events[event].forEach(cb => cb(data)); } }}
const emitter = new EventEmitter();emitter.on('data', (value) => { console.log('Received:', value);});
emitter.emit('data', 'hello');
// Listener returning async resultclass AsyncQueue { constructor() { this.listeners = []; }
subscribe(listener) { this.listeners.push(listener); }
async process(task) { for (const listener of this.listeners) { await listener(task); } }}When to Use Callbacks
Section titled “When to Use Callbacks”// ✅ Good uses for callbacks// 1. Event listenerselement.addEventListener('click', () => {});
// 2. Array methods[1, 2, 3].map(n => n * 2);
// 3. Simple error-first patternfs.readFile('file.txt', (error, data) => {});
// ❌ Avoid callbacks for:// 1. Complex chains (use Promises/async-await)// 2. Error propagation across multiple levels// 3. Multiple concurrent operations (use Promise.all)