Explore how to write asynchronous JavaScript code using async and await keywords, making it look synchronous. Learn through examples and comparisons with promises.
async and await KeywordsWelcome to the fascinating world of asynchronous programming in JavaScript! In this section, we will delve into the async and await keywords, which are powerful tools that help us write asynchronous code that appears synchronous. This makes our code easier to read and maintain, especially when dealing with operations that take time to complete, such as fetching data from a server or reading files.
Before we dive into async and await, let’s briefly revisit the concept of asynchronous programming. JavaScript is a single-threaded language, which means it can execute one task at a time. However, many operations, like network requests or file I/O, can take a while to complete. If JavaScript waited for each of these tasks to finish before moving on to the next, it would be very inefficient. Instead, JavaScript uses asynchronous programming to handle such tasks, allowing other operations to continue running while waiting for the asynchronous task to complete.
Promises are a foundational concept in JavaScript’s asynchronous programming. A promise represents a value that may be available now, or in the future, or never. It allows us to write code that can handle success or failure of asynchronous operations.
Here is a simple example of a promise:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched successfully!");
}, 2000);
});
promise.then((message) => {
console.log(message);
}).catch((error) => {
console.error(error);
});
In this example, the promise simulates a network request that takes 2 seconds to complete. The then method is used to handle the resolved value, and catch is used to handle any errors.
async FunctionsThe async keyword is used to define an asynchronous function. An async function always returns a promise. If the function returns a value, the promise is resolved with that value. If the function throws an error, the promise is rejected with that error.
Here’s how you define an async function:
async function fetchData() {
return "Data fetched!";
}
fetchData().then((message) => {
console.log(message); // Output: Data fetched!
});
In this example, the fetchData function is an async function that returns a string. This string is automatically wrapped in a promise, which is why we can use then to handle the resolved value.
awaitThe await keyword can only be used inside an async function. It pauses the execution of the function until the promise is resolved or rejected. This allows us to write asynchronous code that looks synchronous, making it easier to read and understand.
Let’s modify our previous example to use await:
async function fetchData() {
let message = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched successfully!");
}, 2000);
});
console.log(message);
}
fetchData();
In this example, await pauses the execution of fetchData until the promise is resolved. Once the promise is resolved, the resolved value is assigned to message, and the function continues executing.
async/awaitTo better understand the benefits of async/await, let’s compare it with promises using a more complex example. Suppose we want to fetch user data from an API and then fetch their posts.
Using Promises:
function fetchUser() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: 1, name: "John Doe" });
}, 1000);
});
}
function fetchPosts(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["Post 1", "Post 2"]);
}, 1000);
});
}
fetchUser()
.then((user) => {
console.log("User:", user);
return fetchPosts(user.id);
})
.then((posts) => {
console.log("Posts:", posts);
})
.catch((error) => {
console.error(error);
});
Using async/await:
async function displayUserData() {
try {
let user = await fetchUser();
console.log("User:", user);
let posts = await fetchPosts(user.id);
console.log("Posts:", posts);
} catch (error) {
console.error(error);
}
}
displayUserData();
As you can see, the async/await version is more readable and easier to follow. It looks like synchronous code, which makes it easier to reason about.
async/awaitError handling with async/await is straightforward. We can use try...catch blocks to handle errors, just like in synchronous code.
async function fetchData() {
try {
let response = await fetch("https://api.example.com/data");
let data = await response.json();
console.log(data);
} catch (error) {
console.error("Error fetching data:", error);
}
}
fetchData();
In this example, if any of the await expressions throw an error, it will be caught by the catch block.
async/awaitTo better understand how async/await works, let’s visualize the flow of an asynchronous operation using a flowchart:
graph TD;
A[Start] --> B[Call async function]
B --> C[Await promise]
C -->|Promise resolved| D[Continue execution]
C -->|Promise rejected| E[Catch error]
D --> F[End]
E --> F
This flowchart illustrates how the execution of an async function is paused at the await expression until the promise is resolved or rejected.
Now it’s your turn! Try modifying the displayUserData function to fetch comments for each post. You can create a fetchComments function that returns a promise resolving to an array of comments for a given post ID. Use async/await to fetch and log the comments for each post.
To deepen your understanding of async/await and asynchronous programming in JavaScript, consider exploring the following resources:
Let’s reinforce what we’ve learned with a few questions:
async keyword do in a function declaration?await affect the execution of an async function?async/await over promises?Congratulations on mastering the async and await keywords! These tools are essential for writing clean and efficient asynchronous code in JavaScript. Remember, this is just the beginning. As you progress, you’ll build more complex and interactive web applications. Keep experimenting, stay curious, and enjoy the journey!