JavaScript Async and Await
Handle async code cleanly with async functions, await, error handling, and parallel execution.
TL;DR
- 01Mark functions
asyncto make them return promises. - 02Use
awaitto pause and unwrap promise values. - 03Wrap awaited code in
try/catchfor error handling.
Tips
- 01Use
Promise.all()for independent async operations to run them in parallel and get faster results. - 02Wrap an async IIFE around top-level code in older environments that don't support top-level await directly.
Warnings
- 01Awaiting operations sequentially when they are independent is slower than running them together with
Promise.all(). - 02An unhandled rejection inside an async function without try/catch crashes Node.js processes by default in current versions.
Async Functions
async keywordAdding async to a function changes its return contract to always be a promise.
async function fetchData() {
return "data";
}
fetchData().then(result => console.log(result));Wrapped return valueAn async function that returns a plain value wraps it in a resolved promise.
await inside asyncYou can use await only inside a function declared with async.
async function getData() {
const response = await fetch("/api/data");
return response.json();
}Cleaner syntaxAsync functions are just a cleaner way to work with promises, nothing more.
Always a promiseEvery async function returns a promise, even one that never uses await.
Await Keyword
Pause and resolveAwait pauses the function and hands back the resolved value, nothing more.
async function getUser(id) {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user;
}Unwraps promisesAwait unwraps the resolved value from a promise automatically.
Scope restrictionAwait only works inside async functions or at a module's top level.
async function getMultiple() {
const [a, b] = await Promise.all([
fetch("/a").then(r => r.json()),
fetch("/b").then(r => r.json())
]);
return [a, b];
}Avoid sequential awaitDo not await independent operations one at a time — use Promise.all instead.
Error Handling
try/catchA rejected await throws synchronously, so try/catch works like it does with normal exceptions.
async function fetchData() {
try {
const res = await fetch("/data");
return await res.json();
} catch (error) {
console.error("Failed to fetch:", error);
return null;
}
}Catches any awaitThe catch block runs when any awaited call inside the try block rejects.
finallyUse finally to run cleanup code regardless of success or failure.
async function withCleanup() {
try {
return await operation();
} finally {
cleanup();
}
}Throwing errorsThrow errors from async functions to propagate them as promise rejections.
Chained catchAttach .catch() to an async call when you prefer promise chaining instead.
fetchData().then(data => process(data)).catch(error => console.error("Failed:", error.message));Parallel Execution
Promise.all()Runs every promise concurrently and resolves once all of them succeed.
async function getUsers() {
const [user1, user2] = await Promise.all([
fetch("/users/1").then(r => r.json()),
fetch("/users/2").then(r => r.json())
]);
return [user1, user2];
}Faster than sequentialRunning promises together is much faster than awaiting each one in turn.
Promise.race()Resolves or rejects as soon as the first promise settles, whichever it is.
const fastest = await Promise.race([api1(), api2()]);Promise.allSettled()Waits for every promise to finish and reports each result, even failures.
Promise.any()Resolves as soon as the first promise succeeds, ignoring earlier rejections.
const result = await Promise.any([
fetch("/endpoint-1").then(r => r.json()),
fetch("/endpoint-2").then(r => r.json())
]);
// Resolves with whichever responds first without rejectingCommon Patterns
Top-level awaitES modules can await at the top level without a wrapping async function.
// In a module
const config = await loadConfig();Chained workflowChain multiple async operations together, each awaiting the previous result.
async function workflow() {
const data = await fetch1();
const result = await process(data);
return await fetch2(result);
}Async IIFEUse an async IIFE for immediate async execution without a named function.
(async () => {
const data = await fetchData();
console.log(data);
})();for-await-ofLoop over async iterables item by item using for-await-of.
for await (const item of asyncIterator()) {
console.log(item);
}In Practice
Combines Promise.all with async/await to fetch a user's profile and stats in parallel, with a fallback if either request fails.
- 01Promise.all() kicks off both fetch calls at the same time instead of one after another.
- 02await pauses until both promises resolve, then the responses are parsed as JSON.
- 03The try/catch block catches a failure in either request and returns a safe fallback.
- 04Total time equals the slowest request, not the sum of both requests.
async function loadDashboard(userId) {
try {
const [profileRes, statsRes] = await Promise.all([
fetch(`/api/users/${userId}`),
fetch(`/api/users/${userId}/stats`),
]);
const profile = await profileRes.json();
const stats = await statsRes.json();
return { profile, stats };
} catch (error) {
console.error('Dashboard load failed:', error);
return { profile: null, stats: null };
}
}
loadDashboard(42).then(data => console.log(data));FAQ
Declaring a function with async makes it automatically return a Promise, even if you return a plain value. This means callers can use .then() or await on it without any extra wrapping.
At the top level of ES modules, you can use top-level await directly. Inside regular scripts or non-async functions, wrap your code in an async function first. Attempting await in a non-async context throws a SyntaxError.
Wrap your awaited calls in a try/catch block. The catch block receives the rejection reason, just like a .catch() handler on a Promise chain. You can also attach .catch() directly to an awaited expression if you only need to handle one specific call.
They are functionally equivalent — async/await is syntactic sugar over Promises that makes asynchronous code read like synchronous code. Use async/await for cleaner control flow and easier debugging. Use Promise chains when composing reusable utility functions or when you prefer a functional style.
Store each async call in a variable without awaiting it immediately. Then pass the resulting Promises to Promise.all() and await that. This kicks off all operations concurrently, so total time equals the slowest operation rather than the sum of all.