Async/await makes JavaScript asynchronous code feel almost synchronous. An async function always returns a promise, and await pauses only that function until the promise resolves. The result? Cleaner flow, easier debugging, and far fewer .then() pyramids. It’s basically promises… but without the drama.
Table of Contents
- What Is Async/Await?
- Why JavaScript Needed Async/Await
- How
asyncFunctions Work - How
awaitWorks Internally - Example 1 — Converting a Promise Chain to Async/Await
- Error Handling with Async/Await
- Parallel vs Sequential Await
- Example 2 — Running Multiple Async Tasks
- Await Inside Loops
- Async/Await and the Event Loop
- Example 3 — Microtasks and Await
- Top-Level Await
- Common Mistakes and Pitfalls
- Advanced Patterns
- FAQ
What Is Async/Await?
If you’ve ever stared at a long chain of .then() calls and thought, “Yaar, there must be a better way,” async/await is that better way. It’s just syntactic sugar over promises, but the experience feels cleaner and more approachable.
async functions always return a promise.await pauses that function until a promise settles.
Simple. Effective. And honestly, a relief after years of callback zig-zags.
Why JavaScript Needed Async/Await
Before async/await, we had two options: callbacks and promises.
Callback Hell
Everyone has seen that triangle-shaped code block at least once in their career. Mine was during an overnight hackathon when I debugged callbacks with half-open eyes and a cold samosa on my desk. Not fun.
Promise Chains
Better than callbacks… but still a little stiff. .then().then().catch() everywhere.
Async/await basically said, “Chill, I got this.”
How async Functions Work
When you add async before a function, JavaScript wraps the return value in a promise automatically.
async function example() {
return 42;
}
example().then(console.log); // 42
Even a simple 42 becomes Promise.resolve(42). Predictable behaviour is nice — especially after dealing with callback-based libraries that behaved differently every other day.
How await Works Internally
await works only inside async functions (except in modules with top-level await).
It:
- Pauses the async function
- Gives control back to the event loop
- Resumes when the promise resolves
Sometimes new developers think await blocks the thread. Nope. The rest of JavaScript keeps running happily.
Example 1 — Converting a Promise Chain to Async/Await
Promise Chain
fetchUser()
.then(user => fetchProfile(user.id))
.then(profile => fetchPosts(profile.id))
.then(posts => console.log(posts))
.catch(err => console.error(err));
Async/Await Version
async function loadUserPosts() {
try {
const user = await fetchUser();
const profile = await fetchProfile(user.id);
const posts = await fetchPosts(profile.id);
console.log(posts);
} catch (error) {
console.error(error);
}
}
loadUserPosts();
Much cleaner. Fewer moving parts. The first time I rewrote a legacy promise chain with async/await, someone from QA said, “Did you do black magic or what?” No magic — just better syntax.
Error Handling with Async/Await
try/catch is far more natural than chained .catch() calls.
async function fetchData() {
try {
const data = await getData();
console.log(data);
} catch (error) {
console.error("Error:", error);
}
}
Feels like normal synchronous code, but still fully asynchronous.
Parallel vs Sequential Await
Sequential Example (Slow)
const a = await taskA();
const b = await taskB();
Parallel Example (Fast)
const [a, b] = await Promise.all([taskA(), taskB()]);
Big performance difference. Good habit to build early.
Example 2 — Running Multiple Async Tasks
async function loadDashboard() {
const userPromise = fetchUser();
const statsPromise = fetchStats();
const notificationsPromise = fetchNotifications();
const [user, stats, notifications] = await Promise.all([
userPromise,
statsPromise,
notificationsPromise
]);
return { user, stats, notifications };
}
Independent tasks? Fire them together. No need to be polite and let one finish before starting the next.
Await Inside Loops
Using await inside loops forces sequential behaviour — sometimes needed, often not.
for (const id of ids) {
await fetchItem(id); // sequential and slow
}
Better:
await Promise.all(ids.map(id => fetchItem(id)));
Unless ordering matters, go parallel.
Async/Await and the Event Loop
await uses the microtask queue behind the scenes. It doesn’t block the thread.
- Async function pauses.
- Promise goes to microtask queue.
- Event loop continues doing its thing.
- Function resumes when ready.
Feels synchronous, but it’s absolutely not.
Example 3 — Microtasks and Await
async function demo() {
console.log("A");
await null;
console.log("B");
}
demo();
console.log("C");
Output:
A
C
B
await null is enough to yield to the microtask queue. Funny how something so tiny changes execution order.
Top-Level Await
const config = await fetchConfig();
initializeApp(config);
Very handy. But yes — can slow module loading if misused. Use carefully.
Common Mistakes and Pitfalls
1. Forgetting async before using await
Happens to everyone at least once. You stare at the syntax error and go, “Arre, haan…”
2. Await inside unnecessary loops
Adds accidental slowness.
3. Mixing callbacks and promises
Leads to inconsistent behaviour.
4. Thinking await blocks the thread
Still non-blocking. Only pauses the async function.
5. No error handling
Unhandled rejections can create annoying production bugs.
Advanced Patterns
1. Timeout with Promise.race
async function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout")), ms)
);
return Promise.race([promise, timeout]);
}
2. Retry with Exponential Backoff
async function retry(fn, retries = 3) {
let attempt = 0;
while (attempt < retries) {
try {
return await fn();
} catch (err) {
attempt++;
await new Promise(r => setTimeout(r, attempt * 100));
}
}
throw new Error("Max retries reached");
}
3. Async Iterators
for await (const chunk of stream) {
console.log(chunk);
}
FAQ
1. Does async/await replace promises?
No — it’s built on them. Promises are still the core.
2. Is async/await faster than promises?
Same performance in most cases. Just nicer to work with.
3. Can I use await outside an async function?
Yes, but only in ES modules using top-level await.
4. Does await block the JavaScript thread?
Nope. It only pauses the async function.
5. When should I use Promise.all?
Whenever tasks are independent and can run in parallel.


