Async/Await
Reviewed & published by Brayan K
How async functions and await let JavaScript wait for promises without blocking: error handling with try/catch, running work in parallel with Promise.all, async loops, and debugging async code.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- How JavaScript handles asynchronous code
- Create Promises and understand their states
- Write async functions with async/await
- Handle errors with try/catch in async code
- Run tasks in parallel with Promise.all()
- Debug async code and avoid common pitfalls
💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:
- Download Node.js to run JavaScript on your computer
- Use your browser's Developer Console (Press F12) to test code snippets
- Create a .html file with <script> tags and open it in your browser
⏳ Real-World Analogy: Async/await is like cooking while doing laundry:
- • You start the washing machine (async task)
- • Instead of standing there waiting, you go cook dinner
- • await = "pause here until the laundry is done, then continue"
- • Your program doesn't freeze — it just waits efficiently!
🚀 Async/Await – The Power Behind Modern JavaScript
Asynchronous programming is one of the most important concepts in JavaScript. Every real app—YouTube, TikTok, Instagram, Amazon—uses async code to:
| Syntax Style | Example | Readability |
|---|---|---|
| Callbacks (old) | getData(callback) | Hard to follow |
| Promises | .then().catch() | Better, but chained |
| Async/Await ✓ | await getData() | Reads like normal code! |
- ✔ handle user actions
- ✔ communicate with APIs
- ✔ wait for servers
- ✔ process large tasks without freezing
Without async programming, every website would freeze anytime the browser waited for a fetch request, a large calculation, a slow database, an image load, or anything that takes more than a few milliseconds.
JavaScript used to rely on callbacks, then Promises, and finally the modern solution: async/await.
🌟 Why Async/Await Is a Game Changer
- makes async code look like normal code
- improves readability
- removes callback hell
- is easier to debug
- works perfectly with Promises
- powers most modern JS apps
You MUST learn async/await to become a real developer—frontend, backend, or full stack.
🔥 Core Concept: JavaScript Doesn't Wait
JavaScript is single-threaded, meaning it runs one thing at a time. If something takes long (like waiting for a server), JS does NOT pause the entire app.
- the event loop
- Promisified async operations
Async/await is simply a nicer way to control that behavior.
🧠 Promises (The Foundation of Async/Await)
Before async/await, everything used Promises.
A Promise is a value that will exist… in the future.
A Promise can be:
- pending → still waiting
- resolved → success
- rejected → error
Example:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Success!");
}, 1000);
});
promise.then(result => console.log(result));🎯 Intro to Async Functions
When you write async before a function:
- The function ALWAYS returns a Promise
- Anything returned is automatically wrapped in a Promise
- You can use await inside it
async function hello() {
return "Hello!";
}
hello().then(msg => console.log(msg));Because the function returns a Promise, .then() works.
⏳ Await – Waiting the Modern Way
await pauses the function until the Promise resolves.
async function getData() {
const result = await fetch("/api/data");
console.log("Done:", result);
}This reads like normal synchronous code but is completely asynchronous.
🧱 The Mental Model of Await
Think of await like:
- 🛑 "Pause here until the data is ready."
- ▶️ "Then continue."
But this pause happens ONLY inside the async function—the rest of the program keeps running normally!
⚠️ Important Rule
You can only use await inside an async function.
async function example() {
await fetch("/data");
}await fetch("/data"); // ❌ Error outside async function🔥 Real Example: Simulating API Fetch
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function loadUser() {
console.log("Loading...");
await delay(1000);
console.log("User data loaded!");
}
loadUser();Loading...
(1 second later)
User data loaded!🛑 Error Handling with try/catch
Async errors use the same structure as synchronous code:
async function getUser() {
try {
const data = await fetch("/api/user");
return data;
} catch (error) {
console.error("Failed:", error);
}
}This is much cleaner than .catch() chains.
💥 Promise.all — Parallel Execution
Run multiple async tasks at the same time:
const [user, posts] = await Promise.all([
fetch("/api/user"),
fetch("/api/posts")
]);This saves time since both requests happen simultaneously.
⚡ Sequential vs Parallel Example
await delay(100);
await delay(100);
// Total time: 200msawait Promise.all([delay(100), delay(100)]);
// Total time: 100ms🏗️ Real-World Use Case: Loading User Dashboard
A dashboard page may load:
- notifications
- recent activity
async function loadDashboard() {
const [user, notifications, messages] = await Promise.all([
fetch("/api/user"),
fetch("/api/notifications"),
fetch("/api/messages")
]);
return { user, notifications, messages };
}Modern apps do EXACTLY this.
🧪 Debugging Async Code
- console.time()
- console.timeEnd()
console.time("loading");
await loadDashboard();
console.timeEnd("loading");Also, Chrome DevTools shows async call stacks perfectly.
📚 Deep Dive: Event Loop + Async
- Synchronous code executes first
- Promises get pushed to microtask queue
- Event loop checks for completed microtasks
- Awaited code resumes
- Browser events, rendering, and other tasks run
This is why async code never blocks the UI.
🧱 Using Async/Await with Fetch API
async function fetchUser() {
const response = await fetch("https://api.example.com/user");
const data = await response.json();
return data;
}🧱 Async Loops
Loop through async operations carefully.
items.forEach(async (item) => {
await save(item); // Doesn't actually wait!
});for (const item of items) {
await save(item);
}🧨 Advanced Topic: Async Recursion
Async functions can call themselves:
async function countdown(n) {
if (n === 0) return;
console.log(n);
await delay(1000);
await countdown(n - 1);
}⚡ Real-World Async/Await Patterns
Async/await isn't just for tutorials. Every REAL website uses it constantly:
- YouTube loading recommendations
- TikTok loading videos
- Instagram loading stories
- Amazon fetching product data
- Games loading player stats
- Chat apps loading messages
🎮 Real Example: Loading Game Data
Imagine a game website that loads player stats, inventory, matches, and friends:
The user gets faster loading, smoother UX, and instant dashboard updates.
🛒 Real Example: E-Commerce
When loading a product page, an online store needs to fetch multiple pieces of data:
Amazon does this but with 50+ APIs at once.
🧠 Understanding the Event Loop at a Deeper Level
JavaScript's event loop works like this:
- Run all synchronous code first - Loops, functions, logs, DOM changes
- Put "future tasks" into queues Promises → microtask queue
- setTimeout / timers → task queue
- rendering tasks → render queue
- When synchronous tasks finish - Event loop checks: Microtasks → PROMISE callbacks
- Timers / I/O
- Re-rendering
- Repeat forever
This is why async/await NEVER blocks the UI.
🔥 Misconceptions About Async/Await
- ❌ "await blocks JavaScript" - No — await blocks only the async function, not the thread
- ❌ "await makes code slow" - Actually, sequential awaits slow code. Parallel awaits speed it up
- ❌ "we don't need Promises anymore" - Async/await is just a wrapper around Promises. They are inseparable
🧩 Combining Async/Await With Timers
Delays, animations, loading screens — all use this pattern:
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function demo() {
console.log("Step 1");
await wait(1000);
console.log("Step 2");
}This mimics how apps handle loading skeletons, shimmer effects, countdowns, and animations.
🛠️ Error Handling Masterclass
1. Logging Errors Remotely
async function safeCall(promise) {
try {
return await promise;
} catch (err) {
fetch("/log/error", {
method: "POST",
body: JSON.stringify({ message: err.message })
});
throw err;
}
}2. Retrying Failed API Calls
If an API fails, retry instead of crashing:
async function retry(fn, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch {
if (i === attempts - 1) throw new Error("Failed after retries");
}
}
}3. Timeouts (Avoid Infinite Waiting)
function withTimeout(promise, ms) {
let timer = new Promise((_, reject) =>
setTimeout(() => reject("Timeout!"), ms)
);
return Promise.race([promise, timer]);
}Avoids broken UI during slow servers.
📥 Downloading Large Files with Progress
Async/await lets you stream chunks:
async function downloadFile(url) {
const res = await fetch(url);
const reader = res.body.getReader();
let received = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
received += value.length;
console.log("Downloaded:", received);
}
}This is how Google Drive, Discord, and Steam display progress bars.
🔍 Detecting Slow APIs
Great for dashboards, admin tools, monitoring apps:
async function timed(fn) {
const start = Date.now();
const result = await fn();
console.log("Time:", Date.now() - start, "ms");
return result;
}🎯 Async/Await Patterns You MUST Know
1. Guarding Requests
Only fetch if needed:
async function safeFetch(url) {
if (!url.startsWith("https://")) return null;
return await fetch(url);
}2. Sequential Queue Processing
Like processing videos, images, uploads, conversions:
async function processQueue(queue) {
for (const task of queue) {
await task();
}
}3. Async Memoization (caching)
Great for expensive API calls:
const cache = {};
async function getCached(url) {
if (cache[url]) return cache[url];
const data = await fetch(url).then(r => r.json());
cache[url] = data;
return data;
}🔧 Async/Await + DOM (Real Web Development)
Updating UI during loading:
async function loadProfile() {
loadingSpinner.style.display = "block";
const data = await fetch("/user").then(r => r.json());
loadingSpinner.style.display = "none";
username.textContent = data.name;
}Perfect for login pages, dashboards, profile loading, news websites, stock data.
🧩 Async in Loops: FULL Breakdown
❌ forEach does NOT work with await
items.forEach(async item => {
await save(item); // does NOT wait
});✔ Process in batches (FAST + SAFE)
async function batchProcess(items, size = 5) {
for (let i = 0; i < items.length; i += size) {
const batch = items.slice(i, i + size);
await Promise.all(batch.map(save));
}
}This is used by Shopify syncing, email marketing platforms, social media scrapers, and video processing systems.
📘 Best Practices
- ✔️ Always use try/catch for error handling
- ✔️ Use Promise.all() for parallel operations
- ✔️ Avoid sequential awaits when not necessary
- ✔️ Handle timeouts for slow APIs
- ✔️ Use for...of for async loops
- ✔️ Cache expensive API calls
- ✔️ Show loading states in UI
// Practice async/await with this example
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function fetchUserData() {
try {
console.log("Fetching user...");
await delay(1000);
const user = {
name: "Alice",
age: 30,
email: "[email protected]"
};
console.log("User loaded:", user);
return user;
} catch (error) {
console.error("Error:", error);
}
}
// Parallel execution with Promise.all
async function fetchAllData() {
console.log("\nFetching multiple items...");
const results = await Promise.all([
delay(500).then(() => "Data 1"),
delay(300).then(() => "Data 2"),
delay(400).then(() => "Data 3")
]);
console.log("All data loaded:", results);
}
// Run the async functions
fetchUserData().then(() => fetchAllData());🎯 Practice Challenge
- 1️⃣ Create an async function that simulates fetching data with a 2-second delay
- 2️⃣ Use Promise.all() to fetch 3 pieces of data in parallel
- 3️⃣ Add proper error handling with try/catch
- 4️⃣ Create a retry function that attempts an operation 3 times
- 5️⃣ Build a function that races two promises and returns the fastest
🏁 Recap
- ✅ Promises and how they work
- ✅ Async functions and await keyword
- ✅ Error handling with try/catch
- ✅ Parallel execution with Promise.all()
- ✅ Real-world patterns and use cases
- ✅ Event loop and asynchronous behavior
- ✅ Advanced error handling and retries
- ✅ Working with async loops and batches
Async/await is the foundation of modern JavaScript. Once you master it, you can build real, production-ready applications with confidence.
📋 Quick Reference — Async/Await
| Concept | Syntax |
|---|---|
| Async Function | async function getData() { ... } |
| Await | const data = await fetch(url); |
| Error Handling | try { ... } catch (err) { ... } |
| Parallel | await Promise.all([p1, p2]); |
| Delay | await new Promise(r => setTimeout(r, 1000)); |
Lesson Complete — Async/Await!
You now understand the single most important concept for modern web apps: handling asynchronous operations cleanly and efficiently.
Practice quiz
What does an async function always return?
- The raw value
- undefined
- A Promise
- A callback
Answer: A Promise. Marking a function async makes it always return a Promise; any returned value is wrapped automatically.
Where can you use the await keyword?
- Only inside an async function
- Anywhere in any function
- Only at the top of a file
- Only inside a Promise constructor
Answer: Only inside an async function. await is only valid inside an async function; using it elsewhere is an error.
What does await do?
- Blocks the entire thread
- Cancels the Promise
- Converts a value to a callback
- Pauses the async function until the Promise settles
Answer: Pauses the async function until the Promise settles. await pauses only that async function until the Promise resolves; the rest of the program keeps running.
Which statement about 'await blocks JavaScript' is correct?
- It blocks the whole thread
- It blocks only the async function, not the thread
- It blocks all timers
- It blocks rendering forever
Answer: It blocks only the async function, not the thread. The lesson clears up this misconception: await pauses the async function, not the single thread.
How do you handle errors in async/await code?
- With try/catch
- Only with .catch()
- With if/else
- Errors cannot be caught
Answer: With try/catch. Async errors use the same try/catch structure as synchronous code, which is cleaner than .catch() chains.
What does Promise.all let you do?
- Run async tasks one after another
- Cancel a Promise
- Run multiple async tasks in parallel
- Retry a failed task
Answer: Run multiple async tasks in parallel. Promise.all runs tasks in parallel, so two 100ms delays finish in about 100ms instead of 200ms.
Why does forEach NOT work well with await?
- It throws a syntax error
- The async callbacks are not actually awaited by forEach
- It only runs once
- forEach is deprecated
Answer: The async callbacks are not actually awaited by forEach. forEach does not wait for the async callbacks; use a for...of loop to await each iteration.
Which loop correctly awaits each async operation in sequence?
- items.forEach(async i => await save(i))
- items.map(await save)
- while (await items)
- for (const item of items) { await save(item); }
Answer: for (const item of items) { await save(item); }. A for...of loop with await processes each item sequentially and actually waits.
Two sequential 'await delay(100)' calls take about how long total?
- 100ms
- 200ms
- 50ms
- 0ms
Answer: 200ms. Sequential awaits add up: 100ms + 100ms = 200ms; running them in parallel with Promise.all takes about 100ms.
How is async/await related to Promises?
- It replaces Promises entirely
- It has nothing to do with Promises
- It is a wrapper around Promises — they are inseparable
- It only works without Promises
Answer: It is a wrapper around Promises — they are inseparable. Async/await is syntactic sugar over Promises; you still need to understand Promises.
Continue this course
- Previous: ES6+ Features
- Next: Fetch API — Load data from external APIs and display it on your page
- Quick reference: JavaScript cheat sheet › Async
- From the blog: Async/Await in JavaScript Explained · Understanding JavaScript Closures