JavaScript Async/Await
Async/Await is a modern and powerful feature in JavaScript that simplifies working with asynchronous operations. It allows you to write asynchronous code that reads like synchronous code—making it easier to understand, maintain, and debug. Under the hood, it’s built on top of Promises, but offers a cleaner syntax.
What is Async/Await?
In JavaScript, some tasks take time to complete—like fetching data from the internet or waiting for a timer. Instead of stopping everything while these tasks finish, JavaScript uses something called asynchronous code to keep things running smoothly.
async and await are special keywords introduced in ES2017 that make writing this asynchronous code much easier and cleaner.
- async is used to mark a function as asynchronous. That means the function will always return a Promise (a placeholder for a value that will be ready in the future).
- await can only be used inside an async function. It tells JavaScript to “wait” for the result of a Promise before continuing to the next line of code.
Using Async Functions
In JavaScript, you can make any function asynchronous by adding the async keyword before it. This tells JavaScript that the function will return a Promise, even if it looks like it's just returning a regular value.
Here's a simple example:
async function greet() {
return "Hello!";
}
greet().then(message => console.log(message)); // Output: Hello!
async function greet() {
return "Hello!";
}
greet().then(message => console.log(message)); // Output: Hello!
Let’s break this down:
- greet() is an async function. When called, it automatically returns a Promise.
- Even though it just returns a string ("Hello!"), JavaScript wraps it in a Promise behind the scenes.
- We use .then() to get the result when the Promise resolves, and then print it to the console.
Awaiting Promises
Once you have an async function, you can use the await keyword inside it to pause the code and wait for a Promise to finish.
This is super helpful because it lets you write code that runs step-by-step, even though it’s still asynchronous behind the scenes.
Here's a simple example where we wait for a message to appear after a short delay:
function delayMessage() {
return new Promise(resolve => {
setTimeout(() => {
resolve("Message after 2 seconds");
}, 2000);
});
}
async function showMessage() {
const message = await delayMessage();
console.log(message);
}
showMessage();
// output: Message after 2 seconds
function delayMessage() {
return new Promise(resolve => {
setTimeout(() => {
resolve("Message after 2 seconds");
}, 2000);
});
}
async function showMessage() {
const message = await delayMessage();
console.log(message);
}
showMessage();
// output: Message after 2 seconds
🔍 Let's break it down:
- delayMessage() is a function that returns a Promise. It waits 2 seconds, then gives back a message.
- await delayMessage() pauses the showMessage() function until the message is ready.
- After the Promise resolves, the message is stored in the message variable and then printed to the console.
✅ The benefit? You can write code that looks like it's running in order, but it’s still handling things like delays or data loading behind the scenes.
Error Handling with try/catch
When you're working with async and await, things can still go wrong — like a network request failing or bad data being returned.
Instead of using .catch() like with regular Promises, you can use a try / catch block to handle errors in a clean and readable way.
Here's an example where we try to fetch some data from an API:
async function fetchData() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error fetching data:", error);
}
}
fetchData();
async function fetchData() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error fetching data:", error);
}
}
fetchData();
🔍 What’s happening here:
- The code inside the try block runs normally. We use await to wait for the API response and then convert it to JSON.
- If anything goes wrong — like the server is down or there's a typo in the URL — the catch block will run.
- Inside catch, we log an error message so we can understand what happened.
✅ Using try/catch makes it easy to handle problems without crashing your whole app.
Example 1: Using Async/Await with try/catch to Fetch User Data
Instead of chaining then() calls, you can write asynchronous code that looks like regular, step-by-step instructions using async and await. Errors are handled cleanly with try/catch.
const getUserId = () => {
return new Promise((resolve, reject) => {
const success = true; // change to false to simulate error
setTimeout(() => {
if (success) {
resolve(101); // User ID
} else {
reject("Failed to get user ID.");
}
}, 1000);
});
};
const getUserProfile = (userId) => {
return new Promise((resolve, reject) => {
const success = true; // change to false to simulate error
setTimeout(() => {
if (success) {
resolve({ id: userId, name: "Tom", age: 25 });
} else {
reject("Failed to get user profile.");
}
}, 1000);
});
};
async function showUser() {
try {
const id = await getUserId();
console.log("User ID:", id);
const profile = await getUserProfile(id);
console.log("User Profile:", profile);
} catch (error) {
console.error("Error:", error);
}
}
showUser();
const getUserId = () => {
return new Promise((resolve, reject) => {
const success = true; // change to false to simulate error
setTimeout(() => {
if (success) {
resolve(101); // User ID
} else {
reject("Failed to get user ID.");
}
}, 1000);
});
};
const getUserProfile = (userId) => {
return new Promise((resolve, reject) => {
const success = true; // change to false to simulate error
setTimeout(() => {
if (success) {
resolve({ id: userId, name: "Tom", age: 25 });
} else {
reject("Failed to get user profile.");
}
}, 1000);
});
};
async function showUser() {
try {
const id = await getUserId();
console.log("User ID:", id);
const profile = await getUserProfile(id);
console.log("User Profile:", profile);
} catch (error) {
console.error("Error:", error);
}
}
showUser();
How it works:
- getUserId() simulates fetching a user ID after 1 second, returning a promise that resolves or rejects.
- getUserProfile() simulates fetching the user profile using that ID, again with a 1-second delay.
- The async function showUser() uses await to pause until each promise resolves.
- If everything succeeds, it logs the user ID and profile in order.
- If any promise rejects, the catch block catches the error and logs it.
Output
User ID: 101
User Profile: { id: 101, name: "Tom", age: 25 }
User ID: 101
User Profile: { id: 101, name: "Tom", age: 25 }
This example shows how async/await makes asynchronous code easier to read and manage, especially when you have to do tasks one after another.
Example 2: Fetching User Data Concurrently with Async/Await and Promise.all
Sometimes, you want to run multiple asynchronous tasks at the same time and wait for all of them to finish. You can do this with Promise.all() combined with async/await.
const getUserId = () => {
return new Promise((resolve) => {
setTimeout(() => resolve(101), 1000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve) => {
setTimeout(() => resolve([
{ id: 1, userId, title: "Post 1" },
{ id: 2, userId, title: "Post 2" }
]), 1500);
});
};
const getUserProfile = (userId) => {
return new Promise((resolve) => {
setTimeout(() => resolve({ id: userId, name: "Tom", age: 25 }), 1000);
});
};
async function showUserData() {
try {
const userId = await getUserId();
console.log("User ID:", userId);
// Run both requests concurrently
const [profile, posts] = await Promise.all([
getUserProfile(userId),
getUserPosts(userId)
]);
console.log("User Profile:", profile);
console.log("User Posts:", posts);
} catch (error) {
console.error("Error:", error);
}
}
showUserData();
const getUserId = () => {
return new Promise((resolve) => {
setTimeout(() => resolve(101), 1000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve) => {
setTimeout(() => resolve([
{ id: 1, userId, title: "Post 1" },
{ id: 2, userId, title: "Post 2" }
]), 1500);
});
};
const getUserProfile = (userId) => {
return new Promise((resolve) => {
setTimeout(() => resolve({ id: userId, name: "Tom", age: 25 }), 1000);
});
};
async function showUserData() {
try {
const userId = await getUserId();
console.log("User ID:", userId);
// Run both requests concurrently
const [profile, posts] = await Promise.all([
getUserProfile(userId),
getUserPosts(userId)
]);
console.log("User Profile:", profile);
console.log("User Posts:", posts);
} catch (error) {
console.error("Error:", error);
}
}
showUserData();
How it works:
- getUserId() fetches the user ID with a 1-second delay.
- After getting the ID, getUserProfile() and getUserPosts() run at the same time using Promise.all().
- The function waits until both promises are resolved, then logs the profile and posts.
- If any promise fails, the catch block catches the error.
Output
User ID: 101
User Profile: { id: 101, name: "Tom", age: 25 }
User Posts: [
{ id: 1, userId: 101, title: "Post 1" },
{ id: 2, userId: 101, title: "Post 2" }
]
User ID: 101
User Profile: { id: 101, name: "Tom", age: 25 }
User Posts: [
{ id: 1, userId: 101, title: "Post 1" },
{ id: 2, userId: 101, title: "Post 2" }
]
This example shows how async/await combined with Promise.all() helps run multiple async tasks concurrently, improving performance.
Frequently Asked Questions
What is async/await in JavaScript?
What is async/await in JavaScript?
Async/await is syntax in JavaScript that allows you to write asynchronous code in a way that looks synchronous, making it easier to read and maintain.
How does async/await work with promises?
How does async/await work with promises?
The async keyword makes a function return a promise, and the await keyword pauses the execution until the promise resolves, allowing for easier asynchronous flow control.
Can I use try/catch with async/await?
Can I use try/catch with async/await?
Yes, try/catch blocks can be used with async/await to handle errors from asynchronous operations, providing a cleaner way to catch exceptions.
What happens if I forget to use await?
What happens if I forget to use await?
If you forget to use await before a promise, the function will not wait for the promise to resolve and may result in unexpected behavior or unhandled promises.
Is async/await supported in all browsers?
Is async/await supported in all browsers?
Async/await is supported in all modern browsers, but for older browsers, you may need to use transpilers like Babel to convert async/await syntax to compatible code.
What's Next?
Congratulations! You've completed the JavaScript tutorials and built a strong foundation in the core concepts. To truly solidify your knowledge and sharpen your skills, the best next step is to dive into hands-on practice. Working through real-world examples will help you apply what you've learned and take your JavaScript skills to the next level.