Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

The event loop and timers

Node.js runs your JavaScript in a single thread. While it waits for a file or a network response, it does not sit idle: the system does the waiting, and when the result is ready its handler is put into a queue. The event loop is the mechanism that takes those handlers out one by one and runs them. You saw the general idea in the tutorial; here is what Node.js adds.

Execution order

First all synchronous code runs, then microtasks (promise handlers), and only then timers and I/O handlers:

console.log('1. synchronous code');
 
setTimeout(() => console.log('3. timer'), 0);
 
Promise.resolve().then(() => console.log('2. promise'));
 
console.log('1. synchronous code again');

The lines are printed in the order of their numbers: a timer with zero delay still waits until synchronous code and microtasks are done.

Older code uses process.nextTick(fn) — Node.js's own queue, similar to microtasks. Its order relative to promises depends on whether the file is CommonJS or an ES module, so new code uses the standard queueMicrotask(fn) instead.

setImmediate

setImmediate() runs a function on the next turn of the loop, right after I/O is handled. Inside a file-read callback it is guaranteed to fire before setTimeout(..., 0):

import { readFile } from 'node:fs';
 
readFile(import.meta.filename, () => {
  setTimeout(() => console.log('timer'), 0);
  setImmediate(() => console.log('setImmediate'));
});

It prints "setImmediate", then "timer". In the main module code their order is not guaranteed, so do not rely on it.

Promise-based timers

The node:timers/promises module provides timer versions that return a promise. With them a pause can be awaited:

import { setTimeout as sleep } from 'node:timers/promises';
 
console.log('Publishing the post...');
await sleep(500);
console.log('Done');

Half a second passes between the lines. The name sleep is chosen at import so it is not confused with the ordinary setTimeout.

Repeating on a schedule

setInterval() calls a function again and again, clearInterval() stops it:

let count = 0;
 
const timer = setInterval(() => {
  count++;
  console.log(`Checking for new comments #${count}`);
  if (count === 3) clearInterval(timer);
}, 200);

Three lines are printed 0.2 seconds apart, then the program exits.

Common mistake

A long synchronous computation blocks the event loop: while it runs, Node.js handles no timers and no requests. On a server this means every visitor waits:

const start = Date.now();
setTimeout(() => console.log(`The timer was ${Date.now() - start - 10} ms late`), 10);
 
// Imitating heavy work: 300 ms without a break
while (Date.now() - start < 300) {}

The 10 ms timer fires only after the loop — almost 300 ms late. Heavy computations are moved to a separate thread — see the lesson on worker threads.

Thanks for reading!

Donate to the site — we work hard for you.