Skip to content

Intermediate · Asynchronous JavaScript · topic 36 of 61 · 25 min

Why async, and callbacks

Explanation

JavaScript runs your code on one thread, so it must not sit and wait for slow things like the network or a disk. Instead, it starts the slow job and gives it a callback: a function to call when the job is done. Callbacks work, but nesting many of them leads to "callback hell", which promises later fix.

One thread, many waits

A thread is one line of work that runs instructions one after another. JavaScript runs your code on a single main thread. If that thread stopped for two seconds waiting for a server, the whole page would freeze: no scrolling, no clicks. Node.js would stop serving every other user during the wait.

So slow operations are asynchronous (async): you START them, your code continues immediately, and you are told LATER when they finish. The waiting happens outside your code, in the browser or in Node's system layer.

Synchronous versus asynchronous

javascript
console.log('1. Order placed');
setTimeout(() => {
  console.log('3. SMS sent');      // runs later
}, 1000);
console.log('2. Showing receipt');
Prints 1, 2, then 3 a second later. setTimeout does not pause the program.

Callbacks

A callback is a function you pass to another function so that it can call you back when something happens. You have already used callbacks with map and sort; there they are called immediately. With async APIs they are called later.

javascript
function fakeFetchUser(id, callback) {
  setTimeout(() => callback({ id, name: 'Asha' }), 100);
}
fakeFetchUser(7, (user) => console.log('Got', user.name));
console.log('Request sent');
Prints Request sent first, then Got Asha.

Error-first callbacks in Node

Older Node.js APIs use a convention: the callback's first argument is an error (or null), and the result comes second. You must always check the error first.

javascript
const fs = require('node:fs');
fs.readFile('orders.csv', 'utf8', (err, text) => {
  if (err) {
    console.error('Could not read file:', err.message);
    return;
  }
  console.log('Lines:', text.split('\n').length);
});

Callback hell

When each step needs the result of the previous one, callbacks nest inside callbacks. The code drifts to the right, errors must be handled at every level, and the flow is hard to follow. This shape is called callback hell or the pyramid of doom.

javascript
getUser(id, (err, user) => {
  if (err) return fail(err);
  getOrders(user.id, (err, orders) => {
    if (err) return fail(err);
    getInvoice(orders[0].id, (err, invoice) => {
      if (err) return fail(err);
      sendEmail(user.email, invoice, (err) => {
        if (err) return fail(err);
        console.log('Done!');
      });
    });
  });
});

There is also a trust problem called inversion of control: you hand your function to someone else's code and hope it calls it exactly once, not twice, not never. Promises (next topic) solve both problems.

Careful: A common beginner mistake is trying to RETURN an async result: function getUser() { setTimeout(() => { return user; }, 100); } returns undefined, because the outer function has finished long before the timer fires. The result can only be used inside the callback, or through a promise.
Think of it like this: Async is like ordering at a busy food court. You do not stand at the counter blocking everyone while your biryani is cooked. You get a buzzer (the callback), go and find a seat, and the buzzer calls you when the food is ready.
Tip: You will still meet callbacks in event handlers (button.addEventListener('click', fn)) and timers. For one-time results like "the file is read" or "the server replied", modern code uses promises.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Why async, and callbacks · 1/6

Async and callbacks

In this lesson you will see why JavaScript needs asynchronous code, and meet callbacks.

Prefer a video by a teacher? Find free videos on “javascript callbacks asynchronous explained callback hell” on YouTube ↗

In real life: A reminder service

A clinic booking system sends a reminder 30 minutes before each appointment. It does not loop and wait; it schedules a callback with a timer for each appointment and goes on serving other requests. When the timer fires, the callback sends the SMS.

javascript
function scheduleReminder(patient, msFromNow, send) {
  setTimeout(() => send(`Reminder for ${patient}`), msFromNow);
}
scheduleReminder('Meena', 50, (text) => console.log(text));
console.log('Booking saved');
Prints Booking saved, then Reminder for Meena.

Practice (0/2 done)

Task 1 · Code · Javascript

Write delayedDouble(n, callback) that calls callback(n * 2) after about 10 milliseconds using setTimeout.

Task 2 · Code · Javascript

Write parseLater(text, callback) using the error-first style: after ~5 ms, call callback(null, value) with the parsed JSON, or callback(error) if JSON.parse throws.

Quick quiz

Question 1

1.console.log('A'); setTimeout(() => console.log('B'), 0); console.log('C'); What is printed?

Question 2

2.In an error-first callback (err, data), what should you do first?

Question 3

3.Why is JavaScript code asynchronous for network calls?

Question 4

4.What is "callback hell"?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

console.log('A'); setTimeout(() => console.log('B'), 0); console.log('C'); What is printed?

Answer: A C B

Even with 0 ms, the timer callback runs after the current code finishes.

In an error-first callback (err, data), what should you do first?

Answer: Check err

If err is set, data is not valid.

Why is JavaScript code asynchronous for network calls?

Answer: So the single thread is not blocked while waiting

Blocking the main thread would freeze the page or server.

What is "callback hell"?

Answer: Deeply nested callbacks that are hard to read and handle errors in

It describes the pyramid shape of nested async callbacks.

What does this print? console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve().then(() => console.log('C')); console.log('D');Related

Answer: A D C B

Normal code runs first (A, D). Then the microtask queue runs (C), and only then the timer task (B), even with 0 ms.

From Promises and the event loop

Which queue do promise then callbacks use?Related

Answer: Microtask queue

Promise reactions are microtasks.

From The event loop

If a then callback throws, what runs next?Related

Answer: The nearest catch

A throw turns into a rejection that skips to catch.

From Promises

Why can one Node.js process serve many users at once?Related

Answer: It does not wait idle for slow I/O; it serves other requests meanwhile

With non-blocking I/O, Node.js starts a slow job and serves other requests until the result is ready, like one waiter serving many tables.

From Node.js: JavaScript on the server

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next: Promises →

Next steps

  1. 5 more topics in “Asynchronous JavaScript” before its exam: Promises, async and await, Promise.all, allSettled, race and any, The event loop, fetch: talking to servers.
  2. Module 8 exam: Asynchronous JavaScript · 12 questions · 25 min, without help. 6 topics of this module still to finish first.
  3. Then the next module, TypeScript basics. 61 topics left in the course before the final exam.

← Previous: Regular expressions in practice