Skip to content

Asynchronous JavaScript: Callbacks, Promises, async/await

Programming Node.js Difficulty 1/5

Beginning of the lesson

Asynchronous JavaScript

Callbacks, Promises and async/await in Node.js

Big Idea: Node.js runs your code on a single main thread, so it never sits and waits for slow jobs like reading files or calling the network. Instead it starts the slow job, keeps going, and comes back to handle the result later. That "come back later" style is called asynchronous programming.
By the end you will be able to:
  • explain the event loop and why Node is non-blocking,
  • read and write callback-style code (and spot "callback hell"),
  • use Promises with .then() / .catch(),
  • write clean async code with async / await.

1. Blocking vs non-blocking

Imagine a chef who takes an order, then stands frozen at the oven for 20 minutes until the food is ready before serving anyone else. That is blocking — nothing else can happen while one slow task runs.

Node.js works like a smart waiter instead: it puts the food in the oven, sets a timer, and goes on serving other tables. When the timer rings, it brings the food. That is non-blocking. Watch the order of the messages below:

const fs = require('fs');

// Node does NOT wait here - it starts the read and moves on
fs.readFile('note.txt', 'utf8', function (err, data) {
  if (err) throw err;
  console.log('Inside callback:', data);
});

console.log('After readFile call');

// Output:
// After readFile call
// Inside callback: Hello from the file!

The line After readFile call prints first, even though it is written last, because Node did not wait for the file. When the file finishes loading, Node runs the callback function.

2. The event loop

How can one thread do many things "at once"? The secret is the event loop. Your synchronous code runs on the call stack. Slow jobs (timers, file reads, network) are handed to Node's background APIs. When a job finishes, its callback waits in a queue. The event loop keeps checking: "is the stack empty? then take the next callback from the queue and run it."

Call Stack your running code Node APIs timers, files, network Callback Queue (macrotasks: timers) Microtask Queue (promises) Event Loop
The call stack runs your code; finished jobs queue their callbacks; the event loop feeds them back to the stack when it is empty. Microtasks (promises) run before the next macrotask (timers).

There are two waiting lines. Microtasks (resolved Promises) are handled before the next macrotask (like a setTimeout). This ordering explains a classic puzzle:

console.log('1: start');

setTimeout(function () {
  console.log('2: timeout');
}, 0);

Promise.resolve().then(function () {
  console.log('3: promise');
});

console.log('4: end');

// Output:
// 1: start
// 4: end
// 3: promise
// 2: timeout

Both synchronous lines run first (1, 4). Then the Promise callback (3) runs because microtasks come first. The timer callback (2) runs last.

Continue this lesson on YourAIZone

The full lesson has the complete explanation, worked examples, common mistakes, tests with instant checking and an AI tutor. Start right away as a guest, or create an account to keep your progress.

In the full lesson

  • The complete explanation
  • Worked examples
  • Common mistakes and how to avoid them
  • Test questions: 15
  • AI tutor that answers questions about the topic
  • Progress tracking for students, parents and teachers