Photo by Patrick Tomasso on Unsplash
Understanding Callbacks, Promises, and Async/Await in JavaScript
Master Callbacks, Promises, and Async/Await in JavaScript with This Guide
Table of contents
JavaScript provides multiple ways to handle asynchronous operations. Let's delve into callbacks, Promises, and async/await with detailed explanations and examples.
1. Callbacks
A callback is a function passed as an argument to another function, which is then executed after the completion of an asynchronous operation.
Key Points:
A traditional way to handle asynchronous code.
May lead to "callback hell" if not managed well.
Example 1: Basic Callback
function fetchData(callback) {
setTimeout(() => {
console.log("Data fetched.");
callback();
}, 2000);
}
fetchData(() => {
console.log("Callback executed.");
});
// Output:
// Data fetched.
// Callback executed.
Example 2: Nested Callbacks (Callback Hell)
function fetchData(callback) {
setTimeout(() => {
console.log("Step 1: Data fetched.");
callback();
}, 1000);
}
fetchData(() => {
setTimeout(() => {
console.log("Step 2: Data processed.");
setTimeout(() => {
console.log("Step 3: Data saved.");
}, 1000);
}, 1000);
});
// Output:
// Step 1: Data fetched.
// Step 2: Data processed.
// Step 3: Data saved.
2. Promises
A Promise is an object representing the eventual completion or failure of an asynchronous operation. It provides better readability and avoids "callback hell."
Key Points:
Has three states: pending, fulfilled, rejected.
Methods:
.then()
for handling success..catch()
for handling errors..finally()
for cleanup operations.
Example 1: Creating and Using Promises
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
let success = true;
if (success) resolve("Data fetched successfully!");
else reject("Error fetching data.");
}, 2000);
});
};
fetchData()
.then((message) => {
console.log(message); // Output: Data fetched successfully!
})
.catch((error) => {
console.error(error);
});
Example 2: Chaining Promises
const fetchData = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("Step 1: Data fetched."), 1000);
});
};
fetchData()
.then((message) => {
console.log(message);
return new Promise((resolve) =>
setTimeout(() => resolve("Step 2: Data processed."), 1000)
);
})
.then((message) => {
console.log(message);
return new Promise((resolve) =>
setTimeout(() => resolve("Step 3: Data saved."), 1000)
);
})
.then((message) => {
console.log(message);
});
// Output:
// Step 1: Data fetched.
// Step 2: Data processed.
// Step 3: Data saved.
3. Async/Await
async/await
is a modern syntax built on top of Promises, making asynchronous code look and behave like synchronous code.
Key Points:
Functions using
async
automatically return a Promise.await
pauses the execution until the Promise is resolved or rejected.Provides cleaner and more readable code.
Example 1: Basic Async/Await
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
let success = true;
if (success) resolve("Data fetched successfully!");
else reject("Error fetching data.");
}, 2000);
});
};
const handleData = async () => {
try {
const message = await fetchData();
console.log(message); // Output: Data fetched successfully!
} catch (error) {
console.error(error);
}
};
handleData();
Example 2: Sequential Async/Await
const fetchStep1 = () => new Promise((resolve) => setTimeout(() => resolve("Step 1 complete."), 1000));
const fetchStep2 = () => new Promise((resolve) => setTimeout(() => resolve("Step 2 complete."), 1000));
const fetchStep3 = () => new Promise((resolve) => setTimeout(() => resolve("Step 3 complete."), 1000));
const processSteps = async () => {
console.log(await fetchStep1());
console.log(await fetchStep2());
console.log(await fetchStep3());
};
processSteps();
// Output:
// Step 1 complete.
// Step 2 complete.
// Step 3 complete.
Example 3: Parallel Async/Await
const fetchStep1 = () => new Promise((resolve) => setTimeout(() => resolve("Step 1 complete."), 1000));
const fetchStep2 = () => new Promise((resolve) => setTimeout(() => resolve("Step 2 complete."), 1000));
const fetchStep3 = () => new Promise((resolve) => setTimeout(() => resolve("Step 3 complete."), 1000));
const processSteps = async () => {
const results = await Promise.all([fetchStep1(), fetchStep2(), fetchStep3()]);
console.log(results); // Output: [ 'Step 1 complete.', 'Step 2 complete.', 'Step 3 complete.' ]
};
processSteps();
Comparison
Feature | Callbacks | Promises | Async/Await |
Readability | Difficult (callback hell). | Moderate (chaining). | Easy (clean syntax). |
Error Handling | Needs manual handling. | .catch() for errors. | try/catch blocks. |
Syntax | Nested functions. | Chainable methods. | Looks synchronous. |
Performance | Works for small tasks. | Better control. | Best with Promises. |
When to Use Each?
Callbacks: For simple operations (e.g.,
setTimeout
, event listeners).Promises: When you need better error handling and chaining.
Async/Await: When writing complex asynchronous logic to keep the code readable.