process.nextTick, setImmediate, and setTimeout

process.nextTick, setImmediate, and setTimeout

Daily short news for you
  • Privacy Guides is a non-profit project aimed at providing users with insights into privacy rights, while also recommending best practices or tools to help reclaim privacy in the world of the Internet.

    There are many great articles here, and I will take the example of three concepts that are often confused or misrepresented: Privacy, Security, and Anonymity. While many people who oppose privacy argue that a person does not need privacy if they have 'nothing to hide.' 'This is a dangerous misconception, as it creates the impression that those who demand privacy must be deviant, criminal, or wrongdoers.' - Why Privacy Matters.

    » Read more
  • There is a wonderful place to learn, or if you're stuck in the thought that there's nothing left to learn, then the comments over at Hacker News are just for you.

    Y Combinator - the company behind Hacker News focuses on venture capital investments for startups in Silicon Valley, so it’s no surprise that there are many brilliant minds commenting here. But their casual discussions provide us with keywords that can open up many new insights.

    Don't believe it? Just scroll a bit, click on a post that matches your interests, check out the comments, and don’t forget to grab a cup of coffee next to you ☕️

    » Read more
  • Just got played by my buddy Turso. The server suddenly crashed, and checking the logs revealed a lot of errors:

    Operation was blocked LibsqlError: PROXY_ERROR: error executing a request on the primary

    Suspicious, I went to the Turso admin panel and saw the statistics showing that I had executed over 500 million write commands!? At that moment, I was like, "What the heck? Am I being DDoSed? But there's no way I could have written 500 million."

    Turso offers users free monthly limits of 1 billion read requests and 25 million write requests, yet I had written over 500 million. Does that seem unreasonable to everyone? 😆. But the server was down, and should I really spend money to get it back online? Roughly calculating, 500M would cost about $500.

    After that, I went to the Discord channel seeking help, and very quickly someone came in to assist me, and just a few minutes later they informed me that the error was on their side and had restored the service for me. Truly, in the midst of misfortune, there’s good fortune; what I love most about this service is the quick support like this 🙏

    » 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

Me & the desire to "play with words"

Have you tried writing? And then failed or not satisfied? At 2coffee.dev we have had a hard time with writing. Don't be discouraged, because now we have a way to help you. Click to become a member now!

Have you tried writing? And then failed or not satisfied? At 2coffee.dev we have had a hard time with writing. Don't be discouraged, because now we have a way to help you. Click to become a member 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ường3 months ago
quá hay bạn ơi
Reply
Avatar
Xuân Hoài Tống3 months ago
Cảm ơn bạn Cường nha 🙏