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

💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:

⏳ Real-World Analogy: Async/await is like cooking while doing laundry:

🚀 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 StyleExampleReadability
Callbacks (old)getData(callback)Hard to follow
Promises.then().catch()Better, but chained
Async/Await ✓await getData()Reads like normal code!

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

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.

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:

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:

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:

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: 200ms
await Promise.all([delay(100), delay(100)]);
// Total time: 100ms

🏗️ Real-World Use Case: Loading User Dashboard

A dashboard page may load:

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("loading");
await loadDashboard();
console.timeEnd("loading");

Also, Chrome DevTools shows async call stacks perfectly.

📚 Deep Dive: Event Loop + Async

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:

🎮 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:

This is why async/await NEVER blocks the UI.

🔥 Misconceptions About Async/Await

🧩 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

// 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

🏁 Recap

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

ConceptSyntax
Async Functionasync function getData() { ... }
Awaitconst data = await fetch(url);
Error Handlingtry { ... } catch (err) { ... }
Parallelawait Promise.all([p1, p2]);
Delayawait 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