Async/Await
Learn how async/await lets you write asynchronous, Promise-based code that reads like ordinary synchronous code, including error handling and running work in parallel.
Reading Time
20 min
Lesson
Lesson 31 of 48
From Promise Chains to async/await
async/await is syntax built on top of Promises — it doesn't replace them, it makes them easier to read and write. Instead of chaining .then() calls, you mark a function as async and use the await keyword inside it to pause execution until a promise settles. The result reads almost exactly like synchronous code, while still being fully asynchronous under the hood.
async Functions Always Return a Promise
Adding the async keyword before a function declaration changes what it returns: an async function always returns a Promise, no matter what you return inside it. If you return a plain value, JavaScript automatically wraps it in a resolved promise. If you throw an error inside an async function, that error becomes a rejected promise instead of an uncaught exception.
async Functions Wrap Return Values in Promises
Console Output
Click “Run” to see the console output here.
The await Keyword
The await keyword can only be used inside an async function (with one exception covered later). It pauses execution of that function until the promise it's given settles, then either returns the fulfilled value or throws the rejection as an error. Crucially, await only pauses the async function itself — the rest of your program keeps running normally, since JavaScript is still single-threaded and non-blocking underneath.
Converting a .then() Chain to async/await
Console Output
Click “Run” to see the console output here.
Error Handling with try/catch
One of the biggest advantages of async/await is that it lets you handle Promise rejections with an ordinary try/catch block, exactly like synchronous errors from the earlier lesson on error handling. If any awaited promise rejects, execution jumps straight to the catch block — no need for a separate .catch() call chained onto every asynchronous step.
try/catch Around await
Console Output
Click “Run” to see the console output here.
Parallel vs. Serial awaits
A subtle but important trap: awaiting one promise at a time inside a loop or sequence of statements runs those operations one after another, even if they don't depend on each other's results. If the operations are independent, start them all first and then await them together with Promise.all() — this runs them in parallel and can be dramatically faster.
Accidentally Serializing Independent Work
Console Output
Click “Run” to see the console output here.
await Inside Loops
Calling await on a fresh promise inside every iteration of a for loop forces each iteration to wait for the last one to finish, even when the operations have nothing to do with each other. If the iterations are independent, map over the array to start all the promises first, then await Promise.all() on the resulting array — don't await inside the loop itself.
A Brief Note on Top-Level await
Historically, await was only legal inside an async function. Modern JavaScript modules (the ES module system covered in the next lesson) support top-level await, letting you use await directly in a module's top-level code without wrapping it in an async function. This is handy for one-off setup work, like awaiting a database connection before the rest of a module runs — but it's only available inside modules, not in regular scripts.
Top-Level await in a Module
Console Output
Click “Run” to see the console output here.
Mixing Promises and async/await
You don't have to choose one style forever — async/await and raw Promise methods interoperate freely. It's common to write async functions that still use Promise.all(), Promise.race(), or Promise.allSettled() internally, combining the readability of await with the power of the Promise combinators from the previous lesson.