RizTech Academy logo
RizTech Academy
Asynchronous JavaScriptLesson 1 of 720 min

Why JavaScript is asynchronous at all

This is the module with the reputation, and the reputation is earned — not because the syntax is hard, but because it asks you to give up an assumption you have had since module 2: that code runs top to bottom and a function returns its answer.

Start with the problem, before any of the tools.

One thread

JavaScript does one thing at a time. One call stack, one line running. No other code can run while your loop is looping.

On a page, that thread also draws everything. So while your code runs, nothing else happens — no clicks handled, no scrolling, no typing, no animation.

const start = Date.now();
while (Date.now() - start < 5000) {
  // five seconds of work
}

For five seconds the tab is frozen. Buttons do nothing, text cannot be selected, and the browser may offer to kill the page. Everything you built in module 7 stops.

Now think about fetching an order list from a server in Pune, on mobile data. A hundred milliseconds if you are lucky; several seconds if you are on a train. If JavaScript waited for that, the page would freeze every time it asked for anything. That is unacceptable, and avoiding it is the entire reason this module exists.

The way out: do not wait

JavaScript does not wait. It starts the slow thing, carries on with the next line, and deals with the result when it arrives.

console.log('1 before');
setTimeout(() => console.log('3 later'), 0);
console.log('2 after');
1 before
2 after
3 later

setTimeout with a delay of zero still runs last. It is not a queue-jump instruction; it is "run this when the current work is finished".

That is the shape of every asynchronous operation: network requests, timers, file reads, user events. Start now, finish later, do not block.

How it works: the event loop

Three things, and the picture explains every ordering surprise you will meet.

The call stack — the one thread, running your code.

The task queue — work that is ready to run: a timer that has expired, a click handler, a response that has arrived.

The event loop — a rule: when the stack is empty, take the next thing from the queue and run it.

"When the stack is empty" is the whole thing. Your code runs to completion first. Only then does anything queued get a turn. A setTimeout(fn, 0) inside a five-second loop waits the full five seconds.

Two queues, not one, and this is the part that surprises people:

console.log('1 sync start');
setTimeout(() => console.log('4 setTimeout 0'), 0);
Promise.resolve().then(() => console.log('3 promise then'));
console.log('2 sync end');
1 sync start
2 sync end
3 promise then
4 setTimeout 0

Promises go in a separate, higher-priority queue — the microtask queue — and it is drained completely before a single timer runs. So a promise that is already resolved still runs after all your synchronous code, but before a setTimeout(0) queued earlier.

You will not need this daily. You will need it the day something happens in an order you cannot explain.

Who does the actual waiting

If there is one thread, what counts down the timer?

The browser does. setTimeout, fetch and the rest are not JavaScript — they are browser features, as module 7 said of the DOM. You hand the browser a job and a callback; the browser does the work outside your thread and queues your callback when it is done.

So the concurrency is real; it just is not happening in your JavaScript. Node works the same way with the operating system.

This also explains a common disappointment: setTimeout(fn, 100) means "not before 100ms", not "at 100ms". If the thread is busy when the timer expires, your callback waits its turn.

The consequence you must accept

Here is the bit that breaks people's mental model:

function getOrders() {
  let result;

  setTimeout(() => {
    result = 'the data';
  }, 100);

  return result;
}

console.log(getOrders());
undefined

return result runs immediately, long before the callback. The function returned before the answer existed.

No amount of restructuring fixes this. You cannot return a value that has not arrived. There is no "wait here" that does not freeze the page.

So the pattern has to change. Instead of returning the value, you hand over a function to be called with it — module 3's callbacks, which is the next lesson and the beginning of the whole story:

function getOrders(callback) {
  setTimeout(() => {
    callback('the data');
  }, 100);
}

getOrders((data) => console.log(data));
the data

Everything in this module — callbacks, promises, async/await — is a progressively nicer way of writing that one idea.

What is and is not asynchronous

A frequent confusion:

Asynchronous Synchronous
fetch JSON.parse
setTimeout, setInterval array.map, filter, reduce
Event handlers localStorage.getItem
.then, await Everything in modules 2–6

map and filter are not asynchronous. They take a callback, which makes them look similar, but they run it immediately and finish before the next line. A callback does not mean "later" — it means "here is a function"; only the thing you gave it to decides when.

And note localStorage is synchronous, which is why module 7 warned that reading a large blob on every keystroke makes a phone stutter. It blocks the one thread.

Check your work

JavaScript runs on one thread, which on a page is also the thread that draws it. A long loop freezes the tab entirely.

setTimeout(fn, 0) still runs after all synchronous code. Zero means "as soon as the current work finishes", not "now".

The event loop takes work from the queue only when the stack is empty.

Promises use a separate, higher-priority microtask queue, drained before timers — so the order is sync, then .then, then setTimeout.

The browser does the waiting, not JavaScript. setTimeout and fetch are browser features that queue your callback when they finish.

setTimeout(fn, 100) means "not before 100ms."

A function cannot return a value that has not arrived yet — it returns undefined. The fix is to hand over a function to be called with the value.

map and filter are synchronous despite taking callbacks. So is localStorage.

Practice

  1. Freeze a tab on purpose. Run a five-second while loop and try to click a button or select text while it runs. This is what "blocking" means, and experiencing it once explains the rest of the module.
  2. Log three lines around a setTimeout(fn, 0) and predict the order before running it.
  3. Add a Promise.resolve().then(...) to that and predict again. Getting this one wrong first is normal.
  4. Put a setTimeout(fn, 0) before a five-second blocking loop and measure when the callback actually runs. Explain it using the stack and the queue.
  5. Write the undefined bug. Build getOrders() that sets a value in a setTimeout and returns it. Confirm you get undefined, then try to fix it by returning later in the function and confirm that you cannot.
  6. Rewrite it to take a callback and confirm the data arrives.
  7. Use setInterval to log a counter every second, then stop it after five with clearInterval. Note that you need to keep the id it returns.
  8. Decide, for each of fetch, JSON.parse, array.map, addEventListener and localStorage.getItem, whether it is asynchronous. Two of them are commonly guessed wrong.

Next: callbacks — the first solution to this problem, and how it got out of hand.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship