What is the difference between promises and async/await, and how do you handle errors with each?
async/await is cleaner syntax built on top of promises. An async function always returns a promise, and await pauses that function until a promise settles. With promises you handle errors using .catch(); with async/await you use try/catch around the await.
Explain it simply
When you order food online, the app gives you an order ID immediately, even though the food arrives later. That order ID is a promise: a placeholder for a value that will come in the future. It can end in one of two ways: fulfilled (food delivered) or rejected (order cancelled). Until then it is pending.
With plain promises, you write what should happen next using .then() for success and .catch() for failure. With async/await, you write the same steps as if they were normal top-to-bottom code. await means "wait here for this promise, then continue with its value". Under the hood it is still promises. It is not a different mechanism, just easier to read.
Example
The same task written both ways: fetch a user, then their orders.
// With promise chains
function showOrders(userId) {
return getUser(userId)
.then((user) => getOrders(user.id))
.then((orders) => console.log("Orders:", orders.length))
.catch((err) => console.log("Failed:", err.message))
.finally(() => console.log("Done"));
}
// With async/await
async function showOrdersAsync(userId) {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
console.log("Orders:", orders.length);
} catch (err) {
console.log("Failed:", err.message);
} finally {
console.log("Done");
}
}Running independent work in parallel:
async function loadDashboard() {
// Slow: the second request waits for the first.
// const profile = await getProfile();
// const offers = await getOffers();
// Faster: both start together.
const [profile, offers] = await Promise.all([getProfile(), getOffers()]);
return { profile, offers };
}How it works
- An
asyncfunction always returns a promise. Returning a value fulfils it; throwing an error rejects it. awaitpauses only that async function. The rest of the program keeps running; the code afterawaitresumes later as a microtask (see the event loop).- If the awaited promise rejects,
awaitthrows, so a normaltry/catchcan catch it. - A
.catch()at the end of a chain catches a rejection from any earlier step.
Useful helpers:
| Helper | Resolves when | Rejects when |
|---|---|---|
Promise.all | all succeed | any one fails |
Promise.allSettled | all finish, success or failure | never |
Promise.race | the first one settles | the first one to settle fails |
Promise.any | the first one succeeds | all fail |
Real-world use
Calling APIs, reading files in Node, querying databases, and waiting for a UPI payment status are all asynchronous. Most modern codebases use async/await for readability and use Promise.all or Promise.allSettled when several calls can happen at once.
Common mistakes
- Forgetting
await, so you get a pending promise instead of the data. - Awaiting independent calls one after another, which makes the total time the sum of all calls.
- Using
awaitinsideforEach. It does not wait. Usefor...oforPromise.allwithmap. - Leaving a promise without any
.catch()ortry/catch, which causes unhandled rejections. - Wrapping a promise in
new Promiseunnecessarily.
What to say in the interview
"A promise represents a future value and is pending, fulfilled or rejected. async/await is syntax on top of promises: an async function returns a promise, and await pauses that function until the promise settles, then gives me its value or throws its error. With promise chains I handle errors with .catch(), and with async/await I use try/catch. I prefer async/await for readability, but I still use Promise.all when calls are independent, so they run in parallel instead of one after another."
Follow-up questions
- What is the output order if you mix
await,setTimeoutandconsole.log? - What is the difference between
Promise.allandPromise.allSettled? - Can you use
awaitat the top level of a file?
Go deeper
Read these once the basics above feel easy.
return await matters inside try/catch: return promise inside a try block hands the promise straight to the caller, so if it rejects, your catch does not run. return await promise waits first, so the rejection is caught locally. Top-level await is allowed in ES modules (and therefore in modern browsers with type="module" and in Node ESM files), but not in classic scripts or CommonJS.