async / await in Node.js
async/await is syntax sugar over promises that lets you write asynchronous code that reads like synchronous code — top to bottom, with ordinary try/catch and loops. It's the modern default for async work in Node. Crucially, it doesn't replace promises; it's a nicer way to consume them, so everything you know about promises still applies underneath.
The two keywords
Keyword | Does |
|---|---|
| Marks a function; it now always returns a promise |
| Pauses the async function until a promise settles, yielding its value |
async function getUserName(id) {
const user = await fetchUser(id) // pause here until the promise settles
return user.name // returned value becomes the promise's result
}
getUserName(1).then((name) => console.log(name)) // it's still a promise outsideError handling with try/catch
await turns a rejected promise into a thrown error — so the familiar try/catch you'd use for synchronous code now covers async failures too:
async function loadProfile(id) {
try {
const user = await fetchUser(id)
const posts = await fetchPosts(user.id)
return { user, posts }
} catch (err) {
console.error('load failed:', err.message) // catches EITHER await's rejection
throw err // re-throw to let the caller decide
} finally {
console.log('done') // always runs
}
}The sequential trap
// ✗ SLOW — 300ms total (100 + 100 + 100), they don't depend on each other const a = await getA() // wait 100ms const b = await getB() // then wait 100ms const c = await getC() // then wait 100ms // ✓ FAST — ~100ms total, all three run at once const [a2, b2, c2] = await Promise.all([getA(), getB(), getC()])
await in loops
for…of with await runs iterations one at a time — correct for ordered/dependent work, but slow for independent items. For concurrency, map to promises and Promise.all:
// Sequential — one request at a time (use when order/rate matters)
for (const id of ids) {
const user = await fetchUser(id)
console.log(user.name)
}
// Concurrent — all at once (use for independent items)
const users = await Promise.all(ids.map((id) => fetchUser(id)))Top-level await
In ES modules (and the REPL), you can await at the top level — no wrapping async function needed:
Only in ESM (.mjs or "type": "module")
import { readFile } from 'node:fs/promises'
const config = JSON.parse(await readFile('config.json', 'utf8'))
console.log(config.port)