Asynchronous JavaScript

JavaScript is single-threaded, meaning it executes one task at a time.
But modern web applications must handle:

  • API requests
  • Timers
  • Database operations
  • File reading
  • User events
  • Animations

If JavaScript waited for every slow task to finish, the entire webpage would freeze like a locked gate in a storm.

To solve this, JavaScript uses Asynchronous Programming.


1. What is Synchronous JavaScript?

In synchronous code, tasks run line by line.

console.log("Start");

console.log("Middle");

console.log("End");

Output

Start
Middle
End

Each statement waits for the previous one to complete.


2. Problem with Synchronous Code

Imagine downloading data from a server.

console.log("Fetching data...");

// Assume this takes 5 seconds
heavyTask();

console.log("Done");

During those 5 seconds:

  • UI freezes
  • Buttons stop working
  • Website becomes unresponsive

This is bad user experience.


3. What is Asynchronous JavaScript?

Asynchronous JavaScript allows:

“Start a task now, finish it later, and continue executing other code meanwhile.”

Example:

console.log("Start");

setTimeout(() => {
    console.log("Task Finished");
}, 3000);

console.log("End");

Output

Start
End
Task Finished

The timer runs in background while JavaScript continues.


4. How JavaScript Handles Async Operations

JavaScript uses:

  • Call Stack
  • Web APIs
  • Callback Queue
  • Event Loop

These work together like an organized system.


5. The JavaScript Runtime Architecture

Step-by-Step Flow

Call Stack → Web APIs → Callback Queue → Event Loop

(A) Call Stack

The place where functions execute.

Example:

function hello() {
    console.log("Hello");
}

hello();

hello() enters stack → executes → removed.


(B) Web APIs

Provided by browser or Node.js.

Examples:

  • setTimeout
  • fetch
  • DOM events
  • geolocation

These are NOT part of JavaScript itself.


(C) Callback Queue

Completed async tasks wait here.


(D) Event Loop

Checks:

“Is call stack empty?”

If YES → moves callback from queue to stack.


6. Understanding setTimeout()

Syntax

setTimeout(function, delay);

Example:

setTimeout(() => {
    console.log("Hello after 2 seconds");
}, 2000);

Important Logic

Even if delay is 0, it still waits until stack becomes empty.

Example:

console.log("A");

setTimeout(() => {
    console.log("B");
}, 0);

console.log("C");

Output

A
C
B

Because async callbacks always wait for stack clearance.


7. Callbacks in JavaScript

A callback is:

A function passed as an argument to another function.

Example:

function greet(name, callback) {
    console.log("Hello " + name);

    callback();
}

function bye() {
    console.log("Goodbye");
}

greet("Anas", bye);

Output

Hello Anas
Goodbye

8. Asynchronous Callback Example

setTimeout(() => {
    console.log("Data Loaded");
}, 2000);

The function executes later.


9. Callback Hell

When many async operations depend on each other.

Example:

setTimeout(() => {
    console.log("Step 1");

    setTimeout(() => {
        console.log("Step 2");

        setTimeout(() => {
            console.log("Step 3");
        }, 1000);

    }, 1000);

}, 1000);

This pyramid structure becomes difficult to manage.

Problems:

  • Hard to read
  • Hard to debug
  • Hard to maintain

This is called:

“Callback Hell” or “Pyramid of Doom”


10. Promises in JavaScript

Promises were introduced to solve callback hell.

A Promise represents:

Future Success OR Failure

11. Promise States

A Promise has 3 states:

State Meaning
Pending Still running
Fulfilled Success
Rejected Failed

12. Creating a Promise

const promise = new Promise((resolve, reject) => {

    let success = true;

    if(success) {
        resolve("Task completed");
    } else {
        reject("Task failed");
    }

});

13. Consuming Promises

Using .then() and .catch()

promise
    .then((result) => {
        console.log(result);
    })
    .catch((error) => {
        console.log(error);
    });

14. Real Example of Promise

function fetchData() {

    return new Promise((resolve, reject) => {

        setTimeout(() => {
            resolve("Data received from server");
        }, 2000);

    });

}

fetchData()
    .then((data) => {
        console.log(data);
    });

Output after 2 seconds

Data received from server

15. Promise Chaining

fetchData()
    .then((data) => {
        console.log(data);
        return "Next Step";
    })
    .then((msg) => {
        console.log(msg);
    });

16. Handling Errors

fetchData()
    .then((data) => {
        console.log(data);
    })
    .catch((error) => {
        console.log(error);
    });

17. Async and Await

async/await makes asynchronous code look synchronous and cleaner.

Introduced in ES8.


18. Async Function

async function hello() {
    return "Hello";
}

Async functions automatically return promises.


19. Await Keyword

await pauses execution until promise resolves.

Example:

function fetchData() {

    return new Promise((resolve) => {

        setTimeout(() => {
            resolve("Server Data");
        }, 2000);

    });

}

async function getData() {

    const data = await fetchData();

    console.log(data);
}

getData();

Output after 2 seconds

Server Data

20. Error Handling with Try-Catch

async function getData() {

    try {

        const data = await fetchData();

        console.log(data);

    } catch(error) {

        console.log(error);

    }

}

21. Fetch API

Used to request data from servers/APIs.


Basic Fetch Example

fetch("https://jsonplaceholder.typicode.com/users")
    .then((response) => {
        return response.json();
    })
    .then((data) => {
        console.log(data);
    });

22. Fetch Using Async/Await

async function getUsers() {

    const response = await fetch(
        "https://jsonplaceholder.typicode.com/users"
    );

    const data = await response.json();

    console.log(data);
}

getUsers();

23. Why Async/Await is Better

Callback Promise Async/Await
Complex Better Cleanest
Nested Flat Simple
Hard debugging Easier Very easy
Less readable Good Excellent

24. Parallel Async Operations

Promise.all()

Runs multiple promises together.

const p1 = Promise.resolve("A");
const p2 = Promise.resolve("B");
const p3 = Promise.resolve("C");

Promise.all([p1, p2, p3])
    .then((result) => {
        console.log(result);
    });

Output

["A", "B", "C"]

25. Promise.race()

Returns first completed promise.

Promise.race([p1, p2, p3])
    .then((result) => {
        console.log(result);
    });

26. Microtasks vs Macrotasks

Very important interview topic.


Macrotasks

Examples:

  • setTimeout
  • setInterval

Microtasks

Examples:

  • Promise
  • queueMicrotask

Microtasks execute before macrotasks.


Example

console.log("Start");

setTimeout(() => {
    console.log("Timeout");
}, 0);

Promise.resolve().then(() => {
    console.log("Promise");
});

console.log("End");

Output

Start
End
Promise
Timeout

Because:

Microtask Queue > Macrotask Queue

Priority is given to microtasks.


27. Event Loop Visualization

1. Execute Call Stack
2. Check Microtask Queue
3. Execute all Microtasks
4. Check Macrotask Queue
5. Repeat forever

This endless cycle powers asynchronous JavaScript.


28. Real-World Use Cases

Feature Async Used?
API Calls Yes
Chat Apps Yes
Live Notifications Yes
Video Streaming Yes
File Uploads Yes
Database Queries Yes

Without async programming, modern web apps cannot exist.


29. Common Interview Questions

Q1: Is JavaScript synchronous or asynchronous?

JavaScript is:

Single-threaded and synchronous by nature

But it can handle async operations using:

  • Event Loop
  • Web APIs
  • Callbacks
  • Promises

Q2: Difference between callback and promise?

Callback Promise
Function-based Object-based
Causes nesting Cleaner
Hard error handling Better error handling

Q3: Difference between async/await and promise?

async/await is syntactic sugar over promises.

Promises still work underneath.


30. Complete Real-World Example

function loginUser() {

    return new Promise((resolve) => {

        setTimeout(() => {
            resolve("User Logged In");
        }, 2000);

    });

}

function getProfile() {

    return new Promise((resolve) => {

        setTimeout(() => {
            resolve("Profile Data");
        }, 1000);

    });

}

async function app() {

    console.log("Loading...");

    const user = await loginUser();

    console.log(user);

    const profile = await getProfile();

    console.log(profile);

    console.log("Application Ready");

}

app();

Output Flow

Loading...

(after 2 sec)
User Logged In

(after 1 sec)
Profile Data

Application Ready

Final Core Understanding

Asynchronous JavaScript is built on:

1. Call Stack
2. Web APIs
3. Callback Queue
4. Event Loop
5. Promises
6. Async/Await

Master these deeply, and JavaScript begins to feel less like chaos and more like rhythm — like caravans moving through time without blocking each other’s path.

List of Modules