async/await is syntax built on top of promises that lets asynchronous code read almost exactly like synchronous code. This lesson covers the syntax, error handling, and mistakes to avoid.
What Is Async/Await?
Marking a function async makes it always return a promise, even if you don't manually wrap the return value in one. Inside an async function, the await keyword pauses execution until a promise settles, then either returns its resolved value or throws its rejection as a regular JavaScript error.
This means you can write asynchronous logic using ordinary control flow, if, for, try/catch, instead of chains of .then() calls, while still getting all the benefits of promises underneath.
async function getUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return response.json();
}
const user = await getUser(1);
console.log(user.name);
await only pauses the current async function, not the whole program, other code (other requests, timers) continues to run normally.
Async/Await Syntax
async function name() {
try {
const result = await someAsyncCall();
return result;
} catch (error) {
// handle rejection here
}
}
await can only be used inside a function marked async (or at the top level of an ES module).
An async function always returns a promise, wrapping the returned value automatically.
Use try/catch around await to handle rejected promises, just like synchronous errors.
Multiple sequential await calls run one after another; use Promise.all() to run independent ones in parallel.
Async/Await Cheatsheet
Key rules and patterns for writing clean async/await code.
Pattern
Example
Declare async function
async function fn() {}
Async arrow function
const fn = async () => {}
Await a promise
const data = await fetchData();
Handle errors
try { await fn(); } catch (e) { ... }
Parallel awaits
await Promise.all([a(), b()]);
Top-level await (ESM)
const data = await fetchData(); at module top level
Error Handling With try/catch
Unlike raw promise chains, where you attach .catch() at the end, async/await lets you use ordinary try/catch blocks, which is often easier to reason about, especially when multiple await calls and other logic are interleaved.
It's easy to accidentally write slower code by awaiting independent operations one after another instead of starting them all first and awaiting together.
// Slower: waits for each one before starting the next
const a = await fetchA();
const b = await fetchB();
// Faster: both start immediately, run concurrently
const [a2, b2] = await Promise.all([fetchA(), fetchB()]);
Use sequential awaits only when a later call genuinely depends on the result of an earlier one.
Common Mistakes
Forgetting try/catch around await, letting rejected promises crash the process as unhandled errors.
Awaiting independent calls sequentially instead of using Promise.all(), adding unnecessary latency.
Using await inside a regular (non-async) function, which is a syntax error outside ES module top level.
Forgetting that an async function always returns a promise, even when it looks like it returns a plain value.
Key Takeaways
async/await is syntax sugar over promises that reads like synchronous code.
await pauses only the current async function, not the whole program.
Wrap await calls in try/catch to handle rejected promises cleanly.
Use Promise.all() for independent async calls to avoid unnecessary sequential delays.
Pro Tip
If you find yourself writing several await calls back to back with no dependency between them, stop and ask whether Promise.all() would let them run concurrently instead, it's one of the most common easy performance wins in Node.js code.
You now write clean async code with async/await. Next, understand the event loop itself, the mechanism that makes all of this possible.