Callbacks VS Promises
Difference Between Callbacks and Promises: A Mental Model
June 7, 2024
Difference Between Callbacks and Promises: A Mental Model
Callbacks
Mental Model: Imagine you are at a restaurant, and you place an order with the waiter. The waiter (callback function) takes your order (asynchronous operation) to the kitchen. You continue with your conversation (continue executing code) while your food is being prepared. When your food is ready, the waiter returns and serves you the food (executes the callback function).
Key Characteristics:
- Passing a function: You pass a callback function as an argument to another function, which is then called once the asynchronous operation completes.
- Nested calls (callback hell): For multiple asynchronous operations that depend on each other, you end up nesting callbacks inside callbacks, leading to code that is difficult to read and maintain.
Example:
function fetchData(callback) {
setTimeout(() => {
const data = "some data";
callback(data);
}, 1000);
}
fetchData(function (data) {
console.log(data); // "some data"
});
Promises
Mental Model: Imagine you place an order at an online store. The store confirms your order and gives you a tracking number (promise). You can continue with your day, occasionally checking the tracking number to see the status of your order. Once the order is delivered (resolved), you receive the package (handle the fulfilled promise). If there is a problem with the delivery (rejected), you get a notification (handle the rejected promise).
Key Characteristics:
- Chaining: Promises can be chained using
.then()
and.catch()
methods, leading to more readable and maintainable code. - State management: A promise can be in one of three states: pending, fulfilled, or rejected.
- Error handling: Promises provide a cleaner way to handle errors through the
.catch()
method.
Example:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = "some data";
resolve(data);
}, 1000);
});
}
fetchData()
.then((data) => {
console.log(data); // "some data"
})
.catch((error) => {
console.error(error);
});
Comparison:
-
Readability:
- Callbacks: Can lead to "callback hell" with deeply nested structures, making the code hard to read and maintain.
- Promises: Allow chaining with
.then()
and.catch()
, resulting in more readable and linear code.
-
Error Handling:
- Callbacks: Error handling needs to be done manually, and it can become complex with nested callbacks.
- Promises: Provide a straightforward way to handle errors using
.catch()
.
-
Flexibility:
- Callbacks: Limited to handling a single operation at a time.
- Promises: Can easily handle multiple asynchronous operations in sequence or in parallel using
Promise.all()
orPromise.race()
.
Modern JavaScript: Async/Await
Mental Model: Think of async/await as an improved way of handling promises. It allows you to write asynchronous code that looks and behaves like synchronous code, making it even more readable and easier to understand.
Example:
async function fetchData() {
const data = await new Promise((resolve) => {
setTimeout(() => {
resolve("some data");
}, 1000);
});
console.log(data); // "some data"
}
fetchData();
Conclusion
- Callbacks are simple and straightforward but can become unwieldy with complex asynchronous operations.
- Promises offer a more robust and readable way to handle asynchronous operations and provide better error handling.
- Async/Await builds on promises, offering an even cleaner syntax and making asynchronous code easier to read and write.