Async/Await
A cleaner way to write asynchronous code so it reads like normal, step-by-step code.
What is it?
Promises solved the "callback mess" problem, but chaining many .then() calls can still be hard to read. async/await is newer syntax built on top of promises that lets you write asynchronous code that looks synchronous — top to bottom, like normal steps — while still not blocking the rest of the program.
You mark a function as async, and inside it you use await before a promise to pause that function (and only that function) until the promise settles.
Explain like I'm 10
It's like a recipe written as a checklist instead of a tangle of arrows: 'wait for the water to boil, then add pasta, then wait 10 minutes.' Each step waits for the previous one, written in plain, readable order.
Examples
Using async/await
async function getUserData() {
const response = await fetch("/api/user");
const data = await response.json();
return data;
}Handling errors with try/catch
async function getUserData() {
try {
const response = await fetch("/api/user");
const data = await response.json();
return data;
} catch (error) {
console.log("Failed to load user:", error);
}
}How it works
When JavaScript hits await somePromise, it pauses that async function's progress right there — without freezing the rest of the program — and lets other code keep running. Once the promise settles, the function resumes exactly where it left off, with the resolved value in hand (or an error, if it was rejected).
Why does it exist?
async/await exists purely to make promise-based code easier to read and reason about. It doesn't replace promises — it's built entirely on top of them — but it removes a lot of the .then() chaining boilerplate.
When to use it
Use async/await whenever you have a sequence of asynchronous steps that depend on each other — fetch a user, then fetch their orders, then display them — and you want that sequence to read top-to-bottom like ordinary code.
When not to use it
Skip async/await for synchronous code that never needs to wait on anything — adding async gains you nothing there. And when several async tasks don't depend on each other, awaiting them one at a time is slower than starting them together with Promise.all.
Common mistakes
Forgetting the
asynckeyword on a function that usesawaitinside it.Forgetting to wrap
awaitcalls intry/catch, so rejected promises crash the function silently.Using
awaitin a loop when the calls don't actually depend on each other, making things slower than necessary (running them in parallel withPromise.allwould be faster).
Practice exercises
- Easy:
Convert a
.then()-based promise chain into anasync/awaitfunction. - Medium:
Write an async function that fetches data and handles errors with
try/catch. - Hard:
Write an async function that runs three independent async tasks in parallel using
Promise.all, instead ofawait-ing them one by one.
Interview questions
How does `async/await` relate to promises?
It's syntax sugar built entirely over promises — an async function always returns a promise, and await pauses that function's own progress until the promise it's given settles, then resumes with the resolved value (or throws the rejection).
Does an `async` function always return a promise, even if you `return` a plain, non-promise value?
Yes. Whatever you return from an async function is automatically wrapped in a resolved promise if it isn't already one — callers always get a promise back, never the raw value directly, even for a function that never awaits anything.
How do you handle errors in async/await code, and how does that compare to `.catch()` on a promise chain?
Wrap the await calls in a try/catch block, the same way you'd handle a synchronous throw. A rejected awaited promise is converted into a thrown error at that point, so try/catch catches it exactly like .catch() would catch a rejection further down a promise chain — it's the same underlying mechanism with different syntax.
Does `await` block the entire program?
No — it only pauses the current async function's own execution. Control returns to whatever called it, and the rest of the program, including the browser's UI, keeps running normally until the awaited promise settles and the function resumes.
What does `await` actually do, mechanically, when it hits a promise?
It suspends the async function at that point, registers a continuation on the promise (conceptually like a .then()), and immediately returns control to the caller. Once the promise settles, the rest of the function's body is scheduled to resume as a microtask, picking up exactly where it left off.
What happens if you `await` a value that isn't a promise at all, like a plain number?
It works fine — await treats a non-promise value as already resolved, wraps it as if via Promise.resolve(), and the function still yields control briefly (the resumption is still scheduled as a microtask) before continuing with that same value.
What happens if you use `await` inside a function that isn't marked `async`?
It's a syntax error — await is only valid syntax inside an async function (or, in modules, at the top level). The engine won't let you write it in a plain function; you have to add async first.
Can you use `await` at the top level of a file, outside any function?
Yes, in an ES module — this is 'top-level await.' It lets a module perform asynchronous setup before its exports are considered ready, but it comes with a cost: any module that imports it also has to wait for that await to resolve before it finishes loading.
Why is putting `await` inside a `for` loop for several independent async tasks often a performance mistake?
Each iteration waits for the previous call to fully settle before even starting the next one, so the tasks run strictly one after another instead of concurrently. If the tasks don't depend on each other's results, starting them all first and awaiting them together (for example with Promise.all) finishes in roughly the time of the slowest one instead of the sum of all of them.
How do you run several independent async calls in parallel while still writing await-based code?
Start all the calls first without awaiting each one immediately, collecting the returned promises, and then await them together — typically with await Promise.all([...]) — so they all begin running concurrently instead of one only starting after the previous one finishes.
Predict the output: an `async` function logs 'A', then awaits a resolved promise, then logs 'B'; right after calling that function (without awaiting it), the calling code logs 'C'.
The order is 'A', 'C', 'B'. Everything before the first await inside an async function runs synchronously the moment it's called, so 'A' logs immediately. The function then suspends at the await, control returns to the caller, which logs 'C' next; only once the current synchronous code finishes does the suspended function resume and log 'B'.
If an `async` function has no `await` inside it at all, does calling it still behave asynchronously in any way?
Yes — even with zero awaits, an async function's return value is always wrapped in a promise, and any .then() attached to that promise still runs as a microtask rather than synchronously, so callers can never rely on the result being available the instant the call returns.
What happens to an error thrown inside an `async` function if there's no `try/catch` around it?
It doesn't crash the program synchronously — it's converted into a rejection of the promise the async function returns. Whoever called that function needs to handle the rejection, either by awaiting it inside their own try/catch or attaching a .catch(), or it becomes an unhandled promise rejection.
Is an unhandled rejection from an async function any different, mechanically, from one that comes out of a raw `.then()` chain?
No — under the hood they're the same thing. An async function's returned promise rejecting with no handler produces exactly the same 'unhandled promise rejection' as any other rejected promise nobody attached a .catch() (or awaiting try/catch) to.
Does `Array.prototype.forEach` wait for an `async` callback to finish before moving to the next item?
No — forEach doesn't know or care that its callback returns a promise; it fires off every call back-to-back without waiting, so all the async callbacks effectively start almost simultaneously and finish in whatever order their own work completes, not in array order.
Given that `forEach` doesn't await async callbacks, how would you process an array's items one at a time, waiting for each before starting the next?
Use a for...of loop with await inside it: for (const item of items) { await process(item); }. Because a for loop's body genuinely pauses on each await, this processes strictly in order, unlike forEach.
How would you process every item in an array concurrently and collect all the results with async/await?
Map the array to an array of promises by calling the async function on each item without awaiting individually, then await the whole array at once with Promise.all, for example await Promise.all(items.map(process)) — this starts all the work together instead of serially.
Inside a `try` block, is there a meaningful difference between `return await somePromise;` and `return somePromise;`?
Outside a try/catch, generally no — both eventually resolve to the same value from the caller's perspective. Inside a try/catch, though, return await matters: it keeps the function running long enough for a rejection to be caught by that same catch block, whereas a bare return somePromise immediately hands the (still-pending) promise back to the caller, so any later rejection is no longer caught locally.
Debugging: a teammate wrote `items.forEach(async (item) => { await save(item); })` expecting the items to be saved one at a time, in order — what's actually happening, and how would you fix it?
Because forEach ignores the promise its callback returns, all the save() calls are effectively kicked off together rather than sequentially, and forEach itself doesn't wait for any of them to finish before returning. Replacing it with a for...of loop containing await save(item) (for strict order) or Promise.all(items.map(save)) (for concurrency) fixes it, depending on which behavior was actually intended.
Why is code written with async/await generally easier to debug with breakpoints than an equivalent `.then()` chain?
Because it reads and steps like ordinary sequential code — you can set a breakpoint on any line and step over each await one at a time, watching local variables persist naturally, instead of jumping between separate callback functions that each have their own scope and appear as new stack frames in a .then() chain.
Is it valid to use `await` inside a `.then()` callback? Is that a normal pattern to write?
It's only valid if that callback itself is an async function, and while it works, mixing the two styles in the same piece of code is generally discouraged — it's clearer to commit to one style (usually async/await) for a given block of logic rather than switching back and forth.
If an `async` function throws before reaching its first `await`, is that throw synchronous, or does it still produce a rejected promise?
It still produces a rejected promise, not a synchronous throw the immediate caller can catch with a plain try/catch around the call. Because the function is async, JavaScript always wraps its outcome — success or failure — in a promise, even for a throw that happens before any await is reached.
How would you retry a flaky async operation up to a fixed number of times using async/await?
Wrap the attempt in a loop with a try/catch: on success, return the result immediately; on failure, catch the error, and either continue to the next loop iteration to retry or, once the retry limit is reached, rethrow (or return a failure value) instead of retrying again.
What's the benefit of using `Promise.allSettled()` together with async/await over wrapping each individual `await` in its own `try/catch`?
Promise.allSettled() lets several independent async operations run concurrently and reports every outcome — success or failure — without one failure preventing you from seeing the others' results, whereas awaiting each one in its own try/catch (especially sequentially) is more verbose and, if done naively in a loop, can also lose the concurrency benefit.
Predict the output: an `async` IIFE logs 'start', awaits a `setTimeout`-based promise, then logs 'end'; right after invoking it, the outer code logs 'after call'.
'start', 'after call', then 'end' last. 'start' runs synchronously as soon as the IIFE begins; hitting the await suspends it and hands control back, so 'after call' logs next; only once the timer fires and its promise resolves does the IIFE resume and log 'end'.
Can an `async` function be combined with generator syntax, and is that something typical application code reaches for?
Yes — an async generator function (async function*) combines both, yielding values over time with for await...of on the consuming side. It's a more specialized tool, mainly useful for consuming asynchronous streams of values (like paginated API results), and much less common in everyday code than a plain async function.
Why does wrapping every single `await` in the codebase in its own try/catch, versus one try/catch around several sequential awaits, matter for how precisely you can respond to failures?
A single try/catch around several awaits catches a failure from any of them but loses information about which specific step failed unless you inspect the error itself; wrapping each await individually (or tagging errors) lets you respond differently — retry just the failed step, supply a specific fallback — instead of treating the whole block as one all-or-nothing unit.
If two unrelated `await` calls are written back-to-back in an `async` function without `Promise.all`, does the second one start only after the first fully resolves, or do they overlap at all?
The second call's own async work doesn't even begin — the expression that creates its promise isn't evaluated — until the function resumes after the first await, so there's no overlap at all; they run strictly one after the other in real time.
What determines whether the code immediately after calling an `async` function (without awaiting the call) runs before or after the code inside that function?
Everything inside the async function up to its first await runs synchronously before control ever returns to the caller, so any code in the async function before its first await runs first; everything after that first await is deferred, so the caller's subsequent code generally runs before the async function resumes.
Why doesn't marking a function `async` change how long its non-async internal work takes to execute?
async only changes how the function's result is delivered — wrapped in a promise, with pauses at await points — it doesn't make any of the function's own synchronous computation faster or run on a separate thread; a slow synchronous loop inside an async function still blocks everything else exactly as it would in a regular function.
You need to fetch a user, then, only once you have their ID, fetch their orders. Why can't you simply start both fetches at the same time with `Promise.all`?
Because the second request genuinely depends on data (the user's ID) that only exists after the first one completes — there's a real sequential dependency, so awaiting the first call before starting the second is correct here; Promise.all is only a win when the calls are truly independent of each other.