How Node.js Handles Asynchronous Requests (Event Loop)

- Published on

Node.js is widely known for its efficiency in handling I/O, thanks to its event‑driven model. At the core is the event loop, which enables non‑blocking operations on a single main thread. This article explains how Node.js handles asynchronous requests, outlines event‑loop phases, and shows examples.
What is the Event Loop?
The event loop is the backbone of asynchronous processing in Node.js. The main thread coordinates work while time‑consuming tasks are delegated to the OS or to libuv’s thread pool. The loop progresses through phases each tick:
- Timers — callbacks scheduled by
setTimeout
andsetInterval
- I/O callbacks — deferred callbacks from completed I/O
- Idle/Prepare — internal
- Poll — retrieve new I/O events and execute related callbacks
- Check — callbacks scheduled with
setImmediate
- Close callbacks — e.g., socket close events
Microtasks (Promise.then
/queueMicrotask
) run after each phase, before the loop continues.
Asynchronous Programming in Node.js
Callbacks
const fs = require('fs')
console.log('Starting file read')
fs.readFile('README.md', 'utf8', (err, data) => {
if (err) return console.error(err)
console.log('File content length:', data.length)
})
console.log('File reading initiated, but the program continues')
Expected (order):
Starting file read
File reading initiated, but the program continues
File content length: <number>
Promises
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
wait(100).then(() => console.log('Done after 100ms'))
Async/Await
async function run() {
try {
const user = await getUser()
const posts = await getPosts(user.id)
console.log('Posts:', posts.length)
} catch (err) {
console.error('Error:', err)
}
}
Testing the Event Loop Ordering
console.log('Start')
setTimeout(() => console.log('Timeout'), 0)
setImmediate(() => console.log('Immediate'))
Promise.resolve().then(() => console.log('Promise'))
console.log('End')
Expected output (typical):
Start
End
Promise
Timeout
Immediate
Explanation:
Start
/End
run synchronously.- The microtask queue (Promises) runs before advancing the loop.
setTimeout(..., 0)
executes in the next Timers phase;setImmediate
executes in the Check phase of the same tick, soTimeout
usually appears beforeImmediate
in scripts scheduled from the main context.
Why is Node.js Fast for I/O?
Node.js excels for I/O‑heavy workloads due to non‑blocking I/O and delegation to the OS/libuv, allowing the single thread to multiplex many concurrent requests. CPU‑bound tasks should be moved to worker threads or separate services.
Conclusion
Node.js efficiently processes asynchronous operations via the event loop. Understanding phases and scheduling rules—callbacks, Promises, and async/await—helps you predict behavior, avoid blocking work, and design high‑throughput services.