Skip to main content

Command Palette

Search for a command to run...

JavaScript Async

Updated
•6 min read•View as Markdown

1️ What is a Callback?

A callback is a function passed as an argument to another function. It runs after some task is completed.

We use callbacks mainly for:

  • API calls

  • Reading files

  • Timers

  • Database operations

Because JavaScript is asynchronous, some operations take time. Instead of waiting (blocking), JavaScript continues running other code and calls the callback later.

Real Life Example

You order food.
You give your phone number.
When food is ready, they call you.

That phone number function = callback

Example

function fetchData(callback) {
  setTimeout(() => {
    console.log("Data fetched");
    callback(); // callback function call
  }, 2000);
}

function processData() {
  console.log("Processing data....");
}

fetchData(processData);

Output

Data fetched
Processing data....

Problem with Callbacks

Callback Hell (Pyramid of Doom)

When we have multiple async operations, callbacks become nested and messy.

Example

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

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

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

  }, 1000);

}, 1000);

Problems:

  • Hard to read

  • Inversion of Control

  • Hard to maintain

  • Hard to debug

  • Error handling is hard


What is a Promise?

A Promise is an object that represents the future value of the async operation or task:

“I promise I will give you a result later.”

It has 3 states:

  1. Pending – Waiting

  2. Resolved (Fulfilled) – Success

  3. Rejected – Failed

Promise Object is Inmutable.


Promises Fix the Callback Problems:

  • No deep nesting

  • Better error handling

  • Cleaner code

  • Can chain using .then()

  • No inversion of control. Here, we attach the functions; weare not passing the function.


Basic Promise Example

const myPromise = new Promise((resolve, reject) => {
  let success = true;

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

myPromise
  .then(result => {
    console.log(result); // success
  })
  .catch(error => {
    console.log(error); // error
  });

Sequential Order Using Promises

Tasks run one after another.

function step1() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("Step 1 done");
      resolve();
    }, 1000);
  });
}

function step2() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("Step 2 done");
      resolve();
    }, 1000);
  });
}

step1()
  .then(step2)
  .then(() => {
    console.log("All steps completed");
  });

Parallel Order Using Promises

All tasks run at the same time.

Using Promise.all()

const p1 = new Promise(resolve =>
  setTimeout(() => resolve("Task 1"), 2000)
);

const p2 = new Promise(resolve =>
  setTimeout(() => resolve("Task 2"), 1000)
);

Promise.all([p1, p2])
  .then(results => {
    console.log(results); 
    // ["Task 1", "Task 2"]
  });

Promise APIs

1. Promise.all()

  • Waits for all promises

  • Fails if one fails

  • Fail Fast

2. Promise.race()

  • Returns first finished promise < fail / success>
Promise.race([p1, p2])
  .then(result => {
    console.log(result); // Fastest one
  });

3 Promise.allSettled()

  • Waits for all

  • Returns success + failure both -> {status ,reuslt}

Promise.allSettled([p1, p2])
  .then(results => {
    console.log(results);
  });

4 Promise.any()

  • Returns the first successful promise

Problems with Promises

Even though promises are better than callbacks, they still have issues:

1. Long .then() chains

Still hard to read.

2. Error handling confusion

Sometimes .catch() may not catch nested errors.

3. Mixing sync & async logic gets confusing

What is async/await?

async/await It is built on top of promises.

It makes async code look like normal synchronous code.


Example with async/await

function delay(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

async function run() {
  console.log("Start");

  await delay(2000); // wait here

  console.log("After 2 seconds");
}

run();

How async/await Solves Promise Problems

No .then() chaining
Looks clean
Easy error handling with try/catch
More readable


Error Handling Example

function fetchData() {
  return new Promise((resolve, reject) => {
    reject("Something went wrong");
  });
}

async function getData() {
  try {
    const data = await fetchData();
    console.log(data);
  } catch (error) {
    console.log("Error:", error);
  }
}

getData();

Sequential with async/await

async function runSteps() {
  await step1();
  await step2();
  console.log("All done");
}

runSteps();

Parallel with async/await

async function runParallel() {
  const results = await Promise.all([p1, p2]);
  console.log(results);
}

runParallel();

We prefer: async/await

Feature

Callback

Promise

Async/Await

Readability

❌ Hard

😐 Medium

✅ Easy

Error Handling

❌ Messy

😐 Better

✅ Clean

Debugging

❌ Hard

😐 Medium

✅ Easy

Modern Usage

❌ Old

✅ Yes

✅ Best

What is the Event Loop?

JavaScript is single-threaded (one call stack).

The Event Loop allows JavaScript to handle asynchronous tasks (like setTimeout, fetch, Promise) without blocking the main thread.

It decides when async callbacks should run.

Main Components

1. Call Stack

Executes synchronous code (LIFO).

2. Web APIs (Browser/Node APIs)

Handle async operations like:

  • setTimeout

  • fetch

  • DOM events

  • console

  • LocalStorage

3. Microtask Queue

Contains:

  • Promise.then

  • catch

  • finally

  • queueMicrotask

  • Dom Mutation

4. Macrotask Queue (Callback Queue)

Contains:

  • setTimeout

  • setInterval

5. Event Loop

Continuously checks:

If Call Stack is empty →
   Run ALL Microtasks →
   Run ONE Macrotask →
Repeat

Execution Order Rule

Synchronous Code
→ Microtasks
→ One Macrotask
→ Repeat

Example – setTimeout

console.log("Start");

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

console.log("End");

Output

Start
End
Timeout

Even 0ms timeout waits until the stack is empty.

Example – Promise vs setTimeout

console.log("Start");

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

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

console.log("End");

Output

Start
End
Promise
Timeout

Why?

  • Promise → Microtask (higher priority)

  • setTimeout → Macrotask (lower priority)

Microtasks always run before Macrotasks.

Event Loop executes synchronous code first, then all microtasks, then one macrotask — and repeats.


Async/await and Event Loop

async function test() {
  console.log("1");
  await Promise.resolve();
  console.log("2");
}

console.log("3");
test();
console.log("4");

Output

3
1
4
2

await pauses function and moves the remaining code to the Microtask Queue.

Conclusion

  1. JavaScript runs one thing at a time.

  2. Async tasks don’t run immediately.

  3. Microtasks run before macrotasks.

  4. Blocking code stops the event loop.

  5. Promise callbacks have a higher priority than setTimeout.

  6. Callbacks were first → caused callback hell

  7. Promises improved structure

  8. Async/Await made it clean and readable

  9. Today → Use async/await with Promise APIs

15 views