Callbacks & Promises
๐ Callbacks & Promises
๐ Callbacks Revisited
A callback is a function passed to another function, called when an operation completes:
function fetchUser(id, callback) { // Simulate async operation (like database query) setTimeout(() => { const user = { id, name: 'Alice' }; callback(null, user); }, 1000); } // Use it fetchUser(1, (err, user) => { if (err) { console.error('Error:', err); } else { console.log('User:', user); } });
โ ๏ธ Callback Hell (Pyramid of Doom)
When callbacks nest deeply, code becomes hard to read and maintain:
getUser(1, (err, user) => { if (err) { console.error(err); } else { getOrders(user.id, (err, orders) => { if (err) { console.error(err); } else { getOrderDetails(orders[0].id, (err, details) => { if (err) { console.error(err); } else { console.log('Order details:', details); } }); } }); } });
Each nested operation adds another level of indentation. This is hard to debug and modify.
โจ Promises: A Better Way
A Promise represents a value that will be available in the future. It has three states:
Pending
Operation hasn't finished yet. Waiting for completion.
Resolved (Fulfilled)
Operation succeeded. Promise has a value.
Rejected
Operation failed. Promise has an error.
Settled
Either resolved or rejected. No longer pending.
Creating a Promise
const promise = new Promise((resolve, reject) => { // Async operation setTimeout(() => { const success = true; if (success) { resolve('Operation succeeded!'); // Fulfilled } else { reject(new Error('Operation failed!')); // Rejected } }, 1000); }); promise .then(result => console.log(result)) // Runs if resolved .catch(error => console.error(error)); // Runs if rejected
๐ .then() and .catch()
.then() runs when the promise resolves. .catch() runs when it rejects.
function fetchData(id) { return new Promise((resolve, reject) => { setTimeout(() => { if (id > 0) { resolve({ id, data: 'Some data' }); } else { reject(new Error('Invalid ID')); } }, 1000); }); } // Success case fetchData(1) .then(result => console.log('Got:', result)) .catch(error => console.error('Error:', error.message)); // Error case fetchData(-1) .then(result => console.log('Got:', result)) .catch(error => console.error('Error:', error.message));
โ๏ธ Promise Chains
Chain multiple operations. Each .then() passes its result to the next:
Callback Hell vs Promise Chains
Callbacks (Hard to Read)
getUser(1, (err, user) => { if (err) { console.error(err); } else { getOrders(user.id, (err, orders) => { if (err) { console.error(err); } else { console.log(orders); } }); } });
Promises (Readable)
getUser(1) .then(user => getOrders(user.id)) .then(orders => console.log(orders)) .catch(error => console.error(error));
How Promise Chains Work
fetchUser(1) .then(user => { console.log('User:', user); return fetchOrders(user.id); // Returns a promise }) .then(orders => { console.log('Orders:', orders); return orders[0].id; // Return a value (auto-wrapped in promise) }) .then(orderId => { console.log('First order ID:', orderId); }) .catch(error => { console.error('Error in chain:', error); });
.then() receives the resolved value from the previous .then(). If you return a promise, the next .then() waits for it. If you return a value, it's automatically wrapped in a resolved promise.
๐ Parallel Operations: Promise.all()
Run multiple promises in parallel and wait for all to complete:
const p1 = fetchUser(1); const p2 = fetchUser(2); const p3 = fetchUser(3); Promise.all([p1, p2, p3]) .then(([user1, user2, user3]) => { console.log('All users:', user1, user2, user3); }) .catch(error => { console.error('One of them failed:', error); });
Note: If any promise rejects, Promise.all() rejects immediately.
โก First to Finish: Promise.race()
Waits for the first promise to settle (resolve or reject):
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout!')), 5000) ); Promise.race([fetchData(), timeout]) .then(result => console.log('Got result:', result)) .catch(error => console.error('Error or timeout:', error.message));
๐ป Coding Challenges
Challenge 1: Convert Callbacks to Promises
Take a callback-based function and convert it to return a promise. Then chain multiple calls using .then().
Goal: Practice promise creation and chaining.
Challenge 2: Promise.all() for Parallel Operations
Create three promise-returning functions that fetch different data. Use Promise.all() to fetch all three in parallel and log the combined results.
Goal: Understand parallel promise execution.
Challenge 3: Error Handling in Chains
Create a promise chain where one operation fails. Show how the error propagates down to .catch() and how to recover from errors mid-chain.
Goal: Master promise error handling.
Callbacks: Require error checking in every callback. Nest deeply for sequential operations. Hard to debug.
Promises: Error handling with a single .catch(). Cleaner chains. Better error propagation.
Next: async/await makes promises even cleaner โ but understanding promises is the foundation!
๐ฏ What's Next
Promises are powerful, but we can make them even easier to read with async/await โ JavaScript's modern async syntax. We'll explore that in the next chapter.