JavaScript Async and Await
The async and await keywords provide a cleaner way to work with Promises. They make asynchronous code easier to read by allowing Promise-based operations to be written in a style that looks similar to synchronous code.
An async function always returns a Promise, while await pauses execution inside that async function until the awaited Promise settles.
Creating an Async Function
Add the async keyword before a function declaration to make it asynchronous.
async function greet() {
return "Hello JavaScript";
}
greet().then((message) => {
console.log(message);
});
Even though the function returns a normal string, the async keyword automatically wraps that value in a resolved Promise.
Using await
The await keyword waits for a Promise to settle and returns its fulfilled value. It can normally be used inside an async function.
function getData() {
return Promise.resolve("Data loaded");
}
async function loadData() {
const result = await getData();
console.log(result);
}
loadData();
Using await avoids manually creating long then() chains and can make sequential asynchronous operations easier to understand.
Handling Errors with try and catch
Rejected Promises can be handled with try...catch when using await. This provides a familiar structure for handling asynchronous errors.
async function loadUser() {
try {
const response = await fetch("https://api.example.com/user");
const user = await response.json();
console.log(user);
} catch (error) {
console.error("Request failed:", error.message);
}
}
loadUser();
Keeping the await operations inside try allows errors from rejected Promises to be handled in one place.
Running Operations Sequentially
When one operation depends on the result of another, await can be used to execute them in sequence.
async function loadUserOrders() {
const user = await getUser();
const orders = await getOrders(user.id);
console.log(orders);
}
The second operation starts after the first one has completed. This is appropriate when the second operation needs data produced by the first.
Running Independent Operations in Parallel
If asynchronous operations are independent, awaiting them one after another can unnecessarily increase the total time. Promise.all() can run them together.
async function loadDashboard() {
const [users, products, orders] = await Promise.all([
getUsers(),
getProducts(),
getOrders()
]);
console.log(users);
console.log(products);
console.log(orders);
}
This pattern is useful for dashboards and API requests where several pieces of data can be loaded independently.
Using async and await with Fetch
The Fetch API returns a Promise, making it a common use case for async and await.
async function getUsers() {
const response = await fetch("https://api.example.com/users");
if (!response.ok) {
throw new Error("Failed to fetch users");
}
const users = await response.json();
return users;
}
getUsers()
.then((users) => console.log(users))
.catch((error) => console.error(error.message));
The response itself must be converted to JSON asynchronously, so response.json() is also awaited.
Awaiting Normal Values
await can also be used with ordinary values. JavaScript treats the value as an already fulfilled Promise.
async function example() {
const value = await 100;
console.log(value);
}
example();
Avoid Unnecessary Sequential Awaits
A common performance mistake is awaiting independent operations one by one. If the operations do not depend on each other, start them together with Promise.all().
async function loadData() {
const [users, posts] = await Promise.all([
getUsers(),
getPosts()
]);
console.log(users, posts);
}
Top-Level await
Modern JavaScript modules can use await at the top level without wrapping the code inside an async function.
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);
Top-level await is supported in JavaScript modules and is useful when module initialization depends on asynchronous data.
Async and Await Best Practices
Use async functions when working with Promise-based operations. Handle expected failures with try...catch, avoid blocking independent operations unnecessarily, and use Promise.all() when multiple asynchronous tasks can run concurrently.
Keep asynchronous functions focused on a clear task. Returning useful values from async functions also makes them easier to reuse and test.
Summary
async and await make Promise-based JavaScript easier to read and maintain. An async function always returns a Promise, while await retrieves the result of a Promise inside an asynchronous function.
Use sequential await when operations depend on each other, and Promise.all() when independent operations can run at the same time. Combined with try...catch, async and await provide a clean foundation for handling asynchronous JavaScript applications.