process.nextTick, setImmediate, and setTimeout

process.nextTick, setImmediate, and setTimeout

Daily short news for you
  • Now everything is so modern that people can come up with anything. ferretdb.com is an open-source project that transforms the PostgreSQL database into... MongoDB. That's right, you heard it correctly. If you still want to use Postgres but prefer the Mongo query syntax, then ferretdb is for you.

    Oh, besides PostgreSQL, it can also connect to SQLite. Awesome!!! 🙏

    » Read more
  • Dedicating this to those of you using Cursor. PatrickJS/awesome-cursorrules gathers some .cursorrules files to optimize prompts for Cursor. Imagine these files as the System Prompt for Cursor, controlling it according to your wishes in each project.

    The usage is really simple, just copy the appropriate .cursorrules file and place it in the root folder of the project 🥳

    » Read more
  • Gemini 2.5 has just been released, everyone. This is the most advanced model from Google to date, with top-notch reasoning and coding capabilities - as they introduce it.

    Everyone, take a look at the benchmark table; the metrics are all superior to the other competitors. We don't know the price yet, but you can try it for free in Google AI Studio. I guess we’ll wait for it to be integrated into Cursor to see how its coding abilities measure up 😁

    » Read more

The Problem

Anyone who delves deeper into Node.js may have seen articles distinguishing between setTimeout, setImmediate, and process.nextTick. I am no exception! At first, I tried to understand how to use them by reading articles and even Node documentation. However, they were mostly theoretical. It's understandable because to truly understand their differences and usage, you need to understand the event loop.

In reality, I rarely care about the differences between them. That is, the program still runs even if you don't know or understand them, because you may not need to use them. However, if you want your program to run even better, you definitely need to learn how to use them.

Before we dive into this article, I recommend that you spend some time reading up on the concept of the event loop and how it works. Some of my previous articles on this topic are: Kiến trúc Node.js - Event Loop, Tìm hiểu về vòng lặp sự kiện (Event Loop) trong Node.js. Even better, you should read the valuable documentation from Node: The Node.js Event Loop. However, if you want to explore them from my perspective, please continue reading this article!

Event Loop

The event loop is the heart of Node.js, an infinite loop that pushes asynchronous function callbacks back to the call stack and handles them.

Let's recall the six phases of the event loop. One cycle of the event loop must go through the six phases before starting a new one. The six phases are as follows:

Timers phase: Handles callbacks scheduled using setTimeout and setInterval.

Pending callbacks phase: Handles callbacks delayed from previous I/O phases.

Idle, prepare phase: Reserved for Node.js internal operations.

Poll phase: Handles new I/O tasks and checks for completed events. If no events are present, it waits for new events.

Check phase: Handles callbacks from setImmediate.

Close callbacks phase: Handles close events of resources (such as socket.on('close', ...)).

Each phase handles separate events. Node.js divides the phases because of its single-threaded nature. We cannot push events into the loop and expect them to be processed in order, as some events need to be handled before others. Dividing the phases ensures this!

In one loop cycle, Node.js goes through each phase and handles events in it before moving to the next phase. Here, we see that setTimeout is handled in the Timers phase, the first phase of the event loop. Wait! setImmediate is handled in the Check phase, after the Timers phase. Does this mean setTimeout with a timeout of 0 is always executed before setImmediate? Where does the "immediate" name come from?

You've started to step into Node's complexity. The answer to the question is not always. It depends on the usage scenario, and that's the answer.

setTimeout & setImmediate

In the case where no I/O tasks are being handled, i.e., a completely synchronous program. When using setTimeout(fn, 0), the callback function is scheduled in the "timers" phase. If using setImmediate, the callback function will be executed in the "check" phase. According to theory, the setTimeout callback is executed before setImmediate.

setTimeout(() => {
  console.log('setTimeout');
}, 0);

setImmediate(() => {
  console.log('setImmediate');
});

However, in reality, the results of setTimeout and setImmediate will be randomly executed, depending on which phase Node is currently in. This is because when a program first starts, Node has some initial latency before entering a stable state, meaning it's in either the "timers" or "check" phase.

In the case where I/O tasks are present, the order of execution will be different. Always, setImmediate will be executed first because it is executed immediately after the I/O task is completed, as it lies in the "check" phase of the event loop, which occurs right after the "poll" phase (the phase handling I/O tasks).

setTimeout(fn, 0) will only be executed after the event loop has returned to the "timers" phase, meaning it will have to wait for another event loop cycle to reach the "timers" phase.

fs.readFile("README.md", () => {
  setTimeout(() => {
    console.log('setTimeout');
  }, 0);

  setImmediate(() => {
    console.log('setImmediate');
  });
});

Thus, we should use setImmediate to run a callback function immediately after an I/O task is completed.

setImmediate proves to be useful when used to log requests to a file. Let's look at an example of an HTTP server:

res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello World\n");

// Log to file (less important task)
setImmediate(() => {
  fs.appendFile("server.log", `Request: ${req.url}\n`, (err) => {
    if (err) throw err;
    console.log("Logged request to server.log");
  });
});

Many people think that using setImmediate is unnecessary here because fs.appendFile is an asynchronous function and does not affect the current session's response speed. Therefore, one could remove setImmediate to avoid redundant code.

That perspective is correct if only you are using it. In reality, the server receives many requests simultaneously. Using setImmediate pushes the log task to the Check phase, leaving time for the Poll phase – which is handling the primary function callbacks. Doing this significantly improves the program's performance, moving less important tasks to the Check phase, which is after the Poll phase.

setImmediate is also used to reduce the main thread load. In my previous article Hai kỹ thuật nhằm ngăn vòng lặp sự kiện (Event Loop) bị chặn khi xử lý tác vụ nặng (CPU-intensive task), I discussed the "Partitioning" method based on setImmediate to prevent blocking the event loop when handling a large amount of data.

process.nextTick

Let's recall the six phases again. You won't see any phase handling process.nextTick callbacks. Why is that? It's because process.nextTick is a special function whose callbacks are always handled at the beginning of each event loop phase.

Let's take an example. The event loop handles the "timers" phase -> handles process.nextTick callbacks -> handles the "pending callbacks" phase -> handles process.nextTick callbacks... and so on. That means process.nextTick has the highest priority in the event loop.

So, how do we apply process.nextTick? The answer is clear: whenever we want to perform a task with the highest priority, before any phase is handled, to ensure the task is executed immediately. For example, process.nextTick is also often used in the "Partitioning" method, just like setImmediate, but with one difference: while setImmediate is handled only once per loop, process.nextTick is handled six times.

Therefore, in the bcrypt library, which is used for hash calculation – a very CPU-intensive task that can block the event loop – process.nextTick is applied extensively to prevent this behavior.

It is also often used to reduce the main thread load during the initial startup but still wants the configuration loaded right after:

class MyClass {
  constructor() {
    process.nextTick(() => {
      this.loadConfiguration();
    });
  }

  loadConfiguration() {
    // load config here!
  }
}

const myClass = new MyClass();
Premium
Hello

The secret stack of Blog

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

View all

Subscribe to receive new article notifications

or
* The summary newsletter is sent every 1-2 weeks, cancel anytime.

Comments (1)

Leave a comment...
Avatar
Trịnh Cường5 months ago
quá hay bạn ơi
Reply
Avatar
Xuân Hoài Tống4 months ago
Cảm ơn bạn Cường nha 🙏