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.
await
The 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/await
To 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/await
Error 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/await
To 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!