Promises vs async/await
async/await is syntactic sugar over Promises. Both handle asynchronous operations, but async/await reads like synchronous code while Promises use .then() chaining.
Side-by-Side Comparison
| Feature | Promises (.then/.catch) | async/await |
|---|---|---|
| Syntax style | Chain-based (.then/.catch) | Sequential (await keyword) |
| Error handling | .catch() at chain end | try/catch blocks |
| Readability | Can become nested with complex flows | Reads like synchronous code |
| Debugging | Stack traces can be harder to follow | Better stack traces, easier to step through |
| Parallel execution | Natural with Promise.all() | Requires explicit Promise.all() |
Code Examples
Promises (.then/.catch)
- Chain-based API with .then(), .catch(), .finally()
- Each .then() returns a new Promise
- Can handle multiple async operations with Promise.all/race/allSettled
- Error handling via .catch() at end of chain
function fetchUserData(id) {
return fetch(`/api/users/${id}`)
.then(res => res.json())
.then(user => fetch(`/api/posts/${user.name}`))
.then(res => res.json())
.catch(err => console.error('Failed:', err))
}async/await
- Syntactic sugar over Promises — same underlying mechanism
- Makes async code read like synchronous code
- Error handling via try/catch (familiar pattern)
- async function always returns a Promise
async function fetchUserData(id) {
try {
const res = await fetch(`/api/users/${id}`)
const user = await res.json()
const postsRes = await fetch(`/api/posts/${user.name}`)
return await postsRes.json()
} catch (err) {
console.error('Failed:', err)
}
}When to Use Which
async/await
Default choice for sequential async operations, complex error handling, and any code where readability matters. Use for most async code.
Promises (.then/.catch)
Better for parallel operations (Promise.all), simple transformations, and when building composable async utilities. Also needed in non-async contexts.
Common Mistakes
- Using await in a loop instead of Promise.all for parallel operations — sequential awaits are N times slower
- Forgetting that async functions always return a Promise — returning a value wraps it in Promise.resolve()
- Not handling errors — unhandled promise rejections crash Node.js processes
Interview Questions
Can you use await at the top level?
Yes, in ES modules (not CommonJS). Top-level await was introduced in ES2022. In Node.js, the file must use .mjs extension or have "type": "module" in package.json.
What happens if you forget await before a Promise?
The code continues without waiting for resolution. The variable holds the pending Promise object instead of the resolved value. This is a common source of bugs — the code appears to work but processes data incorrectly.
How would you run 5 API calls in parallel with async/await?
Use Promise.all(): `const results = await Promise.all([fetch(url1), fetch(url2), ...])`. Each fetch runs concurrently and await pauses until all resolve. For partial failure tolerance, use Promise.allSettled() instead.