By WebNest Studio Engineering Team
JavaScript Tutorial
Async/Await in JavaScript
async/await is syntax introduced in ES2017 that lets you write asynchronous code that reads like synchronous code, while still being built entirely on top of Promises. It doesn't replace Promises — it's "syntactic sugar" over them, making Promise-based code easier to write and reason about, especially when several async steps depend on each other.
Any function marked async automatically returns a Promise, and inside it, the await keyword pauses execution of that function (without blocking the rest of the program) until the awaited Promise settles.
Why it matters
async and await let asynchronous code be written top to bottom like ordinary code, with normal try and catch for errors. It is the form in which almost all API calls are written today. One common mistake is awaiting independent operations one after another when they could run together, which doubles the waiting time.
How await Relates to Promises
await somePromise is roughly equivalent to calling .then() on that Promise and continuing the function with the resolved value — except it looks like a normal, blocking assignment. If the awaited Promise rejects, await throws that rejection as a regular JavaScript exception at that line, which is why try/catch becomes the natural way to handle async errors instead of .catch() chains.
Error Handling with try/catch
Wrapping await expressions in a try block lets you catch failures exactly where synchronous code would: try { const data = await fetchData(); } catch (err) { /* handle it */ }. Without a surrounding try/catch, a rejected await propagates up as a rejected Promise from the async function itself, which the caller must then handle.
Sequential vs Parallel Awaiting
Awaiting one operation after another inside a loop or a sequence of statements runs them one at a time, which is correct when each step depends on the previous result, but wasteful when they're independent. To run independent async calls concurrently while still using await, start all the Promises first (without awaiting immediately) and then await Promise.all() on them together.
Examples
An async function with await and try/catch, alongside synchronous code
function fetchUserAge(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
id === 1 ? resolve(28) : reject(new Error('User not found'));
}, 100);
});
}
async function printUserInfo(id) {
console.log('Start fetching...');
try {
const age = await fetchUserAge(id);
console.log('Age is', age);
} catch (err) {
console.error('Failed:', err.message);
}
console.log('Done.');
}
printUserInfo(1);
console.log('This logs before "Age is 28" because await does not block.');
Start fetching...
This logs before "Age is 28" because await does not block.
Age is 28
Done.
Sequential awaits vs concurrent awaits with Promise.all
const delay = (label, ms) =>
new Promise((resolve) => setTimeout(() => resolve(label), ms));
async function sequential() {
const a = await delay('A', 100); // waits 100ms
const b = await delay('B', 100); // then waits another 100ms
console.log(a, b, '- sequential took ~200ms total');
}
async function concurrent() {
const pA = delay('A', 100); // both start immediately
const pB = delay('B', 100);
const [a, b] = await Promise.all([pA, pB]); // waits ~100ms total
console.log(a, b, '- concurrent took ~100ms total');
}
sequential();
concurrent();
A B - concurrent took ~100ms total
A B - sequential took ~200ms total
Common Mistakes
- Marking a function
asyncbut forgetting that it always returns a Promise, then trying to use its return value directly instead of awaiting or chaining it. - Awaiting independent operations one after another instead of starting them together and awaiting
Promise.all(), which needlessly slows down the total time. - Forgetting
try/catcharoundawait, letting a rejected Promise crash an unhandled path or silently reject the enclosing async function. - Using
awaitoutside of anasyncfunction (in older environments without top-level await support), which is a syntax error.
Exercise
fetchUser is a stand-in for an API call: it resolves with a user for id 1 and rejects for any other id. Write the async function loadName, which returns the user's name, or the text not found when the request fails.
Expected output: Asha then not found
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id === 1) resolve({ id: 1, name: "Asha" });
else reject(new Error("no such user"));
}, 10);
});
}
async function loadName(id) {
// TODO: await fetchUser(id) and return the name; return "not found" on failure
}
async function main() {
console.log(await loadName(1));
console.log(await loadName(2));
}
main();
Show hints
- Put the
awaitinside atryblock; a rejected promise is thrown as an exception at theawait. - Return
"not found"from thecatchblock.
Show solution
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id === 1) resolve({ id: 1, name: "Asha" });
else reject(new Error("no such user"));
}, 10);
});
}
async function loadName(id) {
try {
const user = await fetchUser(id);
return user.name;
} catch (error) {
return "not found";
}
}
async function main() {
console.log(await loadName(1));
console.log(await loadName(2));
}
main();
Quick quiz
1. What does an async function always return?
- A. undefined
- B. A callback
- C. A promise
- D. The value directly
Show answer
C. A returned value becomes the fulfilled value of that promise.
2. What does await do?
- A. Cancels the promise
- B. Blocks the whole program
- C. Creates a new thread
- D. Pauses the async function until the promise settles, without blocking the rest of the program
Show answer
D. Other code, such as event handlers, keeps running while the function is paused.
3. Two independent requests each take one second. How should they be awaited to finish in about one second?
- A. await Promise.all([first(), second()])
- B. await first(); await second();
- C. first(); second();
- D. await first() + second()
Show answer
A. Awaiting them one after the other takes about two seconds.
Interview questions
How do async and await relate to promises?
Show answer
They are syntax built on promises. An async function always returns a promise. await pauses the function until the promise it is given settles, then gives back the fulfilled value or throws the rejection as an exception, which try and catch can handle. The code after an await behaves like the callback of a then. The result reads like synchronous code.
What are common mistakes with async and await?
Show answer
Forgetting await, so that a promise is used where its value was intended. Awaiting independent operations in sequence instead of with Promise.all. Using await inside forEach, which does not wait; a for...of loop or Promise.all with map is needed. And leaving out error handling, so that a rejection goes unhandled.
Key Points to Remember
- async functions always return a Promise; await pauses only that function, not the whole program.
- await is syntactic sugar over
.then()— a rejected awaited Promise throws, so try/catch is the standard error-handling pattern. - Sequential awaits run one after another; starting Promises first and awaiting
Promise.all()runs them concurrently. - async/await does not replace Promises — every async function is still built on the Promise mechanism underneath.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Shows a live preview of your HTML, CSS and JavaScript. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering