Asynchronous JavaScript
Callbacks, Promises and async/await in Node.js
- 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."
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.