process.nextTick, setImmediate, and setTimeout

process.nextTick, setImmediate, and setTimeout

Daily short news for you
  • For a long time, I have been thinking about how to increase brand presence, as well as users for the blog. After much contemplation, it seems the only way is to share on social media or hope they seek it out, until...

    Wearing this shirt means no more worries about traffic jams, the more crowded it gets, the more fun it is because hundreds of eyes are watching 🤓

    (It really works, you know 🤭)

    » Read more
  • A cycle of developing many projects is quite interesting. Summarized in 3 steps: See something complex -> Simplify it -> Add features until it becomes complex again... -> Back to a new loop.

    Why is that? Let me give you 2 examples to illustrate.

    Markdown was created with the aim of producing a plain text format that is "easy to write, easy to read, and easy to convert into something like HTML." At that time, no one had the patience to sit and write while also adding formatting for how the text displayed on the web. Yet now, people are "stuffing" or creating variations based on markdown to add so many new formats that… they can’t even remember all the syntax.

    React is also an example. Since the time of PHP, there has been a desire to create something that clearly separates the user interface from the core logic processing of applications into two distinct parts for better readability and writing. The result is that UI/UX libraries have developed very robustly, providing excellent user interaction, while the application logic resides on a separate server. The duo of Front-end and Back-end emerged from this, with the indispensable REST API waiter. Yet now, React doesn’t look much different from PHP, leading to Vue, Svelte... all converging back to a single point.

    However, the loop is not bad; on the contrary, this loop is more about evolution than "regression." Sometimes, it creates something good from something old, and people rely on that goodness to continue the loop. In other words, it’s about distilling the essence little by little 😁

    » Read more
  • Alongside the official projects, I occasionally see "side" projects aimed at optimizing or improving the language in some aspects. For example, nature-lang/nature is a project focused on enhancing Go, introducing some changes to make using Go more user-friendly.

    Looking back, it resembles JavaScript quite a bit 😆

    » Read more

Problem

In the previous article, we learned about the six phases of the event loop, their functions, and which types of callback functions are handled in each. Additionally, there is a special phase called process.nextTick, which is not part of the Event loop but has the highest priority.

This seemingly simple concept often confuses many, even experienced developers. So, when process.nextTick, setImmediate, and setTimeout are placed together, which callback do you think executes first?

Revisiting the Event loop

First, let’s revisit the six-phase model of the event loop.

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           poll            │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

Each phase handles specific types of events. We cannot flood the Event loop with events and expect it to process them sequentially; certain events need to be processed before others. Dividing into phases ensures this.

In every loop iteration, Node.js traverses each phase, processing the events in that phase before moving to the next. Here, setTimeout is processed in "timers," located at the top of the Event loop. Meanwhile, setImmediate is processed in "check." Does this mean that a setTimeout with a delay of 0 always executes before setImmediate?

setTimeout & setImmediate

In a synchronous program, meaning no asynchronous functions are called, if you use setTimeout(fn, 0), the callback is scheduled in the "timers" phase. If you use setImmediate(), the callback is scheduled in the "check" phase. Theoretically, the callback of setTimeout should always execute before setImmediate.

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

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

When running the above code, you’ll notice "setTimeout" and "setImmediate" appear in no particular order—sometimes "setTimeout" logs first, other times "setImmediate" does. This inconsistency is because, at the start of a program, Node.js experiences an initial delay before stabilizing—it might begin in the "timers" phase or the "check" phase.

In cases where the program calls an asynchronous function, the execution order differs, with setImmediate always taking precedence because it is executed immediately after the I/O operation completes, as it resides in the "check" phase of the event loop, which occurs right after the "poll" phase (where I/O tasks are processed).

setTimeout(fn, 0) only executes after the event loop cycles back to the "timers" phase, meaning it has to wait for another loop iteration to reach "timers."

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

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

Therefore, we should use setImmediate to run a callback immediately after an I/O task completes.

setImmediate proves useful for logging requests. For example, in an HTTP server:

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

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

Many might think setImmediate is unnecessary here, as fs.appendFile is an asynchronous function and does not affect the current response time. Thus, removing setImmediate could avoid redundant code.

That’s not necessarily correct. In reality, servers often handle numerous simultaneous requests. Using setImmediate pushes the logging task into the "check" phase, leaving time for the "poll" phase—which handles critical task callbacks—thereby improving server performance. Always remember to push less critical tasks to the "check" phase, prioritizing the "poll" phase.

Additionally, setImmediate can reduce the main thread’s load. For more details, refer to the article Two Techniques to Prevent the Event Loop from Being Blocked by CPU-Intensive Tasks. That article discusses a "Partitioning" technique using setImmediate to prevent the Event loop from stalling when processing large amounts of data.

process.nextTick

Looking back at the six phases of the Event loop, you won’t find any phase handling process.nextTick callbacks. This is because process.nextTick is a special phase where its callback is always processed at the beginning of every phase of the event loop.

For instance, after the Event loop finishes processing "timers" callbacks and is about to move to "pending callbacks," it pauses to process process.nextTick callbacks. Once completed, it transitions to "pending callbacks" and so on in subsequent phases. This means process.nextTick has very high priority in the Event loop.

How should we use process.nextTick? The answer is straightforward: whenever you need to perform a task with the highest priority before other phases are processed to ensure immediate execution.

For example, process.nextTick is often applied in "Partitioning" techniques similar to setImmediate, except that each loop processes setImmediate once, whereas process.nextTick can be processed up to six times per loop.

For this reason, libraries like bcrypt, used for hashing—a computationally heavy task with the potential to block the Event loop—extensively apply process.nextTick to prevent such behavior.

Conclusion

In this article, we deeply explored how process.nextTick, setImmediate, and setTimeout operate in Node.js, clarifying their roles in the Event loop. process.nextTick stands out with the highest priority, executing right before transitioning to other Event loop phases, making it an ideal choice for tasks requiring immediate execution. Meanwhile, setImmediate is preferred for handling less critical tasks after completing I/O, reducing the Event loop’s load and optimizing system performance. setTimeout is typically used to schedule execution in subsequent loops but is susceptible to Node.js’s initial delay.

Understanding these differences not only helps developers use callbacks more effectively but also optimizes application performance, especially in multitasking systems with high concurrency demands. Depending on the scenario, choosing the right tool among process.nextTick, setImmediate, and setTimeout can significantly impact how your application performs and responds. Use them wisely to ensure tasks are processed in the correct priority and align with system requirements.

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ường7 months ago

quá hay bạn ơi

Reply
Avatar
Xuân Hoài Tống6 months ago

Cảm ơn bạn Cường nha 🙏