17.2 Operations handling callbacks, promises, and async/await (20 mins)
Asynchronous programming in JavaScript enables the execution of tasks without blocking the main thread, allowing for more responsive and efficient applications. The primary mechanisms to handle asynchronous operations are callbacks, promises, and async/await.
1. Callbacks
A callback is a function passed as an argument to another function, executed after the completion of that function. This approach allows for asynchronous operations by deferring actions until a task is completed.
Example:
function fetchData(callback) {
setTimeout(() => {
const data = 'Sample Data';
callback(data);
}, 1000);
}
function processData(data) {
console.log(`Processing: ${data}`);
}
fetchData(processData);In this example, fetchData simulates an asynchronous operation using setTimeout. After 1 second, it invokes the callback function, passing the retrieved data.
Challenges with Callbacks:
Callback Hell: Nesting multiple callbacks can lead to code that's difficult to read and maintain.
Error Handling: Managing errors across multiple callback levels can be cumbersome.
2. Promises
Promises provide a more elegant way to handle asynchronous operations, representing a value that may be available now, in the future, or never. A promise can be in one of three states: pending, fulfilled, or rejected.
Creating a Promise:
const fetchData = new Promise((resolve, reject) => {
setTimeout(() => {
const data = 'Sample Data';
resolve(data);
}, 1000);
});
fetchData
.then(data => {
console.log(`Processing: ${data}`);
})
.catch(error => {
console.error(`Error: ${error}`);
});Here, fetchData is a promise that resolves after 1 second with the data. The then method handles the fulfilled state, while catch handles any errors.
Advantages over Callbacks:
Chaining: Promises allow for chaining multiple asynchronous operations, leading to more readable code.
Error Handling: Errors can be caught at a single point using
catch.
3. Async/Await
Introduced in ES2017, async/await simplifies working with promises, making asynchronous code appear synchronous.
Using Async/Await:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = 'Sample Data';
resolve(data);
}, 1000);
});
}
async function processData() {
try {
const data = await fetchData();
console.log(`Processing: ${data}`);
} catch (error) {
console.error(`Error: ${error}`);
}
}
processData();In this example, processData is an asynchronous function that waits for fetchData to resolve before proceeding. The try...catch block handles any potential errors.
Benefits:
Readability: Code is more linear and easier to understand.
Error Handling: Synchronous-like
try...catchblocks can be used for error management.
Student Activity
Objective: Convert a callback-based function to use promises and then refactor it using async/await.
Task:
Given the following callback-based function:
function getUserData(callback) { setTimeout(() => { const user = { name: 'Alice', age: 25 }; callback(user); }, 1000); } getUserData(user => { console.log(`User: ${user.name}, Age: ${user.age}`); });Refactor
getUserDatato return a promise.Use
thento log the user's name and age.Further refactor the function to use async/await.
Solution:
Refactoring to Promises:
function getUserData() { return new Promise((resolve, reject) => { setTimeout(() => { const user = { name: 'Alice', age: 25 }; resolve(user); }, 1000); }); } getUserData() .then(user => { console.log(`User: ${user.name}, Age: ${user.age}`); }) .catch(error => { console.error(`Error: ${error}`); });Using Async/Await:
async function displayUserData() { try { const user = await getUserData(); console.log(`User: ${user.name}, Age: ${user.age}`); } catch (error) { console.error(`Error: ${error}`); } } displayUserData();
This exercise demonstrates the evolution from callbacks to promises and finally to async/await, highlighting the improvements in code readability and maintainability.
Last updated