Async Await
This lesson explains Async Await with clear examples, use cases, and best practices so you can use modern JavaScript confidently in real projects.
ES6 Async Await Overview
Async and await make asynchronous JavaScript easier to write and read.
They are built on top of Promises and allow developers to write async code
that looks similar to synchronous code.
Async await is commonly used with Fetch API, API calls, database requests,
file uploads, timers, authentication flows, dashboards, form submissions,
and any operation that takes time to complete.
| Feature | Description |
async | Declares a function that always returns a Promise. |
await | Pauses execution inside an async function until a Promise settles. |
| Error Handling | Uses try, catch, and finally. |
| Built On | Promises |
| Best Use | Readable async code, API calls, sequential and parallel workflows. |
| Common APIs | Fetch API, IndexedDB wrappers, Web APIs, server requests. |
Basic Async Await Example
async function greetUser() {
return "Hello User";
}
greetUser()
.then((message) => {
console.log(message);
});
An async function always returns a Promise, even when it
returns a normal value.
Using await
function waitOneSecond() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Done waiting");
}, 1000);
});
}
async function runTask() {
const result =
await waitOneSecond();
console.log(result);
}
runTask();
The await keyword waits for the Promise to finish before
moving to the next line.
Async Await Syntax
| Pattern | Syntax | Use Case |
| Async Function Declaration | async function loadData() { ... } | Reusable named async functions. |
| Async Function Expression | const loadData = async function() { ... } | Store async function in a variable. |
| Async Arrow Function | const loadData = async () => { ... } | Modern frontend callbacks and services. |
| Await Promise | const data = await promise | Read resolved Promise value. |
| Error Handling | try { await task() } catch (error) { ... } | Handle rejected Promises. |
Async Await with Fetch API
async function loadUsers() {
const response =
await fetch("/api/users");
const users =
await response.json();
console.log(users);
}
loadUsers();
This example waits for the API response, then waits again while converting
the response body to JSON.
Fetch Error Handling with Async Await
async function loadProducts() {
try {
const response =
await fetch("/api/products");
if (!response.ok) {
throw new Error(
"HTTP Error: " + response.status
);
}
const products =
await response.json();
return products;
} catch (error) {
console.error(
"Failed to load products:",
error.message
);
return [];
}
}
Fetch does not reject for HTTP errors like 404 or 500. Check
response.ok and throw an error when the request fails.
try catch finally with Async Await
async function submitForm(data) {
try {
console.log("Submitting form...");
const response =
await fetch("/api/contact", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error("Form submit failed");
}
return await response.json();
} catch (error) {
console.error(error.message);
return null;
} finally {
console.log("Submit process completed.");
}
}
The finally block runs whether the async operation succeeds
or fails. It is useful for hiding loaders, resetting buttons, or cleanup.
Async Arrow Functions
const getUser =
async (id) => {
const response =
await fetch("/api/users/" + id);
return response.json();
}
getUser(101)
.then((user) => {
console.log(user);
});
Async arrow functions are commonly used in services, event handlers,
React hooks, and API utility files.
Sequential Await
async function loadDashboard() {
const user =
await fetch("/api/user")
.then((response) => response.json());
const orders =
await fetch("/api/orders?userId=" + user.id)
.then((response) => response.json());
return {
user,
orders
};
}
Sequential await is useful when the second request depends on the result
of the first request.
Parallel Await with Promise.all()
async function loadPageData() {
const userPromise =
fetch("/api/user")
.then((response) => response.json());
const productsPromise =
fetch("/api/products")
.then((response) => response.json());
const notificationsPromise =
fetch("/api/notifications")
.then((response) => response.json());
const [user, products, notifications] =
await Promise.all(
[
userPromise,
productsPromise,
notificationsPromise
]
);
return {
user,
products,
notifications
};
}
Use Promise.all() when async tasks are independent and can
run at the same time.
Parallel Await with Promise.allSettled()
async function loadOptionalWidgets() {
const results =
await Promise.allSettled(
[
fetch("/api/weather"),
fetch("/api/news"),
fetch("/api/stocks")
]
);
results.forEach((result) => {
if (result.status === "fulfilled") {
console.log("Loaded:", result.value);
} else {
console.log("Failed:", result.reason);
}
});
}
Use Promise.allSettled() when some async tasks can fail
without stopping the entire workflow.
Timeout Pattern with Promise.race()
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(
new Error("Request timed out")
);
}, ms);
});
}
async function loadWithTimeout() {
try {
const response =
await Promise.race(
[
fetch("/api/data"),
timeout(3000)
]
);
return response.json();
} catch (error) {
console.error(error.message);
return null;
}
}
Promise.race() can be used to stop waiting when an operation
takes too long.
Async Await in Loops
async function loadUsersSequentially(ids) {
const users =
[];
for (const id of ids) {
const response =
await fetch("/api/users/" + id);
const user =
await response.json();
users.push(user);
}
return users;
}
Use for...of with await when each task must run
one after another.
Async Await with map()
async function loadUsersInParallel(ids) {
const userPromises =
ids.map(async (id) => {
const response =
await fetch("/api/users/" + id);
return response.json();
});
const users =
await Promise.all(userPromises);
return users;
}
map() with async returns an array of Promises. Use
Promise.all() to wait for all results.
Async Event Handler Example
document
.querySelector("#saveButton")
.addEventListener("click", async () => {
const button =
document.querySelector("#saveButton");
button.disabled =
true;
try {
await submitForm(
{
name: "Alice",
message: "Hello"
}
);
console.log("Saved successfully.");
} catch (error) {
console.error("Save failed.");
} finally {
button.disabled =
false;
}
});
Async event handlers are useful for forms, buttons, modals, file uploads,
and UI workflows that call APIs.
Top-Level Await
// Works in JavaScript modules
const response =
await fetch("/api/settings");
const settings =
await response.json();
console.log(settings);
Top-level await can be used in ES modules. In normal scripts, use an async
function wrapper.
Promises vs Async Await
| Feature | Promises | Async Await |
| Syntax | .then() and .catch() | await with try catch |
| Readability | Good for short chains. | Better for complex flows. |
| Error Handling | .catch() | try catch |
| Parallel Work | Promise.all() | await Promise.all() |
| Return Value | Promise | Promise |
Common Async Await Use Cases
- Loading data from APIs.
- Submitting forms.
- Uploading files.
- Authentication and login flows.
- Loading dashboard widgets.
- Processing multiple requests.
- Retrying failed operations.
- Waiting for browser APIs that return Promises.
Common Async Await Mistakes
- Using
await outside an async function or module. - Forgetting that async functions always return Promises.
- Not using
try catch for rejected Promises. - Running independent requests sequentially instead of in parallel.
- Using async inside
forEach() and expecting it to wait. - Forgetting to check
response.ok with Fetch API. - Blocking UI without loading or error states.
- Not using
finally for cleanup.
Async Await Best Practices
- Use
try catch for error handling. - Use
finally for cleanup and UI reset logic. - Use
Promise.all() for independent parallel tasks. - Use
for...of when async tasks must run sequentially. - Use meaningful function names for async operations.
- Return useful fallback values after errors.
- Show loading, success, and error states in the UI.
- Keep async functions small and focused.
Key Takeaways
async functions always return Promises. await pauses execution inside async functions until a Promise settles. - Use
try catch to handle async errors. - Use
Promise.all() for parallel async work. - Use
for...of instead of forEach() when awaiting inside loops. - Async await makes Promise-based code easier to read and maintain.
Pro Tip
In interviews, explain async await as cleaner syntax over Promises:
async returns a Promise, await waits for a
Promise, try catch handles errors, and
Promise.all() runs independent async tasks in parallel.