How does async/await work in JavaScript?
The Short Answer
async/await is syntactic sugar over Promises that lets you write asynchronous code in a sequential, top-to-bottom style. Under the hood it still uses Promises, but the resulting code is significantly easier to read, write, and reason about.
How It Works
Before async/await, handling multiple dependent async operations meant chaining .then() calls. While functional, deeply nested chains became difficult to follow. async/await flattens this into code that reads like synchronous logic.
Consider fetching a user and then fetching their orders — two dependent async calls.
function getUserOrders(userId: string) {
return fetchUser(userId)
.then(user => fetchOrders(user.id))
.then(orders => console.log(orders))
.catch(err => console.error(err));
}
async function getUserOrders(userId: string) {
try {
const user = await fetchUser(userId);
const orders = await fetchOrders(user.id);
console.log(orders);
} catch (err) {
console.error(err);
}
}
Key insight
The async/await version reads top-to-bottom. Each await pauses only this function until the Promise resolves, then execution continues to the next line.
The async Keyword
Placing async before a function declaration does two things:
What async does
- ✅The function always returns a `Promise` — even if you return a plain value, it gets wrapped automatically
- ✅You can now use the `await` keyword inside the function body
The await Keyword
await pauses execution of the enclosing async function until the awaited Promise settles. It then unwraps the resolved value and returns it.
Important distinction
await only pauses the function it lives in — it does not block the main thread. Other code outside this function continues to execute while the function is suspended.
Error Handling
With raw Promises you chain .catch(). With async/await you use try...catch — the same pattern you already know from synchronous code. This makes error handling consistent and predictable.
async function loadData() {
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
console.error('Failed to load data:', error);
throw error; // re-throw if caller needs to handle it
}
}
Common Mistakes
Sequential awaits when parallel is possible
Using `await` in a loop when requests don't depend on each other forces them to run one-by-one. If each takes 1s and you have 5, that's 5 seconds total.
✅Use `Promise.all()` to run independent requests concurrently — same 5 requests finish in ~1 second.
Forgetting that async functions return Promises
Calling an `async` function without `await` or `.then()` means you get a Promise object, not the resolved value. This leads to subtle bugs where data appears as `[object Promise]`.
✅Always `await` or `.then()` the result of an async function when you need the resolved value.
// ❌ Slow — sequential (5 seconds for 5 users)
for (const id of userIds) {
const user = await fetchUser(id);
results.push(user);
}
// ✅ Fast — parallel (≈1 second for 5 users)
const results = await Promise.all(
userIds.map(id => fetchUser(id))
);
Why Interviewers Ask This
Nearly every frontend app fetches data from APIs. Interviewers use this question to gauge whether you understand JavaScript's async execution model, can handle errors gracefully, and know when to run operations sequentially vs in parallel. Demonstrating awareness of common pitfalls signals you can write reliable, performant async code in production.
Quick Revision Cheat Sheet
async: Makes a function return a Promise and enables `await` inside it
await: Pauses the `async` function until the Promise resolves, returns the value
Error handling: Use `try...catch` instead of `.catch()` chains
Parallel execution: Use `Promise.all()` for independent async operations
Does not block: `await` only pauses the enclosing function, not the main thread