Asynchronous code and events

Do not block the event loop

Recognize CPU-heavy or synchronous work that prevents a Node.js server from handling other clients.

8 minute lesson

~~~

JavaScript callbacks normally run on one event-loop thread. A long calculation or synchronous operation keeps that thread busy, so timers, requests, and completed I/O callbacks must wait.

Node can handle many connections with a small number of threads only when each callback finishes quickly. One request that performs a huge JSON parse, catastrophic regular expression, synchronous file read, or long loop can increase latency for every other client. If user input controls the expensive work, this can become a denial-of-service risk.

Asynchronous APIs separate waiting from JavaScript execution. Network I/O is coordinated by the event loop, while filesystem, crypto, and some other operations use a worker pool. That pool is finite too: flooding it with expensive tasks can delay unrelated work even though the event-loop thread remains free.

Make the delay visible with a heartbeat:

setInterval(() => {
  console.log('heartbeat', Date.now())
}, 100)

const end = Date.now() + 2000
while (Date.now() < end) {
  // Deliberately block the event loop for two seconds.
}

The missing heartbeat timestamps are evidence of event-loop delay, not slow networking. In a real service, measure tail request latency and event-loop delay with tools such as monitorEventLoopDelay() from node:perf_hooks before and after a change.

Choose the remedy based on the work:

  • replace synchronous I/O in request paths with asynchronous APIs
  • bound input size before parsing, matching, compressing, or hashing it
  • split divisible JavaScript work into small batches that yield between them
  • move sustained CPU-heavy JavaScript to worker threads or another process
  • stream large data instead of buffering it all

Worker threads do not make an algorithm cheaper; they isolate CPU work so the main event loop can continue. They also add messaging and memory overhead. Yielding with setImmediate() can improve responsiveness for batchable work but still consumes the same main-thread CPU.

Your action is to run the heartbeat example, measure its largest gap, then replace the single loop with bounded batches that yield using setImmediate(). Compare responsiveness and total completion time.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →