Handle async code cleanly with async functions, await, error handling, and parallel execution.
async to make them return promises.await to pause and unwrap promise values.try/catch for error handling.Promise.all() for independent async operations to run them in parallel and get faster results.Promise.all().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.
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.
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));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 rejectingTop-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);
}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.
Loading a Dashboard with Parallel Requests
Combines Promise.all with async/await to fetch a user's profile and stats in parallel, with a fallback if either request fails.
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));Promise.all() plus try/catch runs independent requests in parallel while still handling failures in one place.