6

This is the Node event loop.

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

I know that Node uses the V8 JS engine and I've learned that V8 has its own event loop that embedders are free to replace or extend. In Chrome for example, there is a macrotask and a microtask queue (the microtask queue has priority over the macrotask queue) and the event loop pushes callbacks from those queues once the call stack of the main thread is empty.

You can queue microtasks on the microtask queue manually using the queueMicrotask function.

I've tried using this function in Node as well and it works. My question is, where are microtasks queued on the event loop ? In what phase are they queued in ? Do they have a phase that is specific for them or are they mixed up with other kinds of callbacks ?

I've tried this code to get an idea of what has priority in the Node event loop :

const hello_world =() => {
    console.log("Hello World 2");
};

queueMicrotask(hello_world);

setTimeout(
    () => {
        console.log("Hello World 3");
    },
    0
);

console.log("Hello World 1");

/*
OUTPUT:

Hello World 1
Hello World 2
Hello World 3
*/

So, I can see that microtasks have priority over timers (which are considered macrotasks in the chrome event loop) but where are the microtasks enqueue in the event loop in Node ?

And furthermore, what has priority over what in the Node event loop ? I know that in Chrome for example, the microtask queue has priority over the macrotask queue, but what phase has priority over what phase in the Node event loop ?

David
  • 624
  • 1
  • 6
  • 21
  • "(the microtask queue has priority over the macrotask queue)" it doesn't. That's not a priority system here, (in an HTML event loop) it's just that there is a microtask checkpoint when the callstack is empty, and that this checkpoint will run every microtasks queued (even the ones queued from that checkpoint). A priority system would imply heuristics to determine what should be executed next, and for instance it would allow that after n times such task is executed, it executes one other task. Here there is no choice, no heuristic and no priority. – Kaiido Jul 30 '22 at 15:26
  • 1
    Also, setTimeout is very bad for testing anything since in node (and recently in Chrome, but I think they fixed it there now), there is a minimum 1ms timeout. – Kaiido Jul 30 '22 at 15:31
  • @Kaiido I've read the following three articles that suggest that the microtask queue has priority over the macrotask queue: https://blog.insiderattack.net/javascript-event-loop-vs-node-js-event-loop-aea2b1b85f5c https://serhiikoziy.medium.com/event-loop-in-chrome-browser-72bd6c8db033 https://javascript.info/event-loop. Please give me a detailled answer if you don't agree because it's hard for me (and other users as well) to understand this concepts from a small comment – David Jul 30 '22 at 15:40
  • 1
    I unfortunately lack the time to explain clearly how the event loop in browsers works, and I lack knowledge about how node's event loop works to post a valid answer to that question. But to make my first comment clearer, in a browser you can be sure that the microtask queue will be emptied after any task, actually it will be visited many times per event loop iteration. I'm saying this is not a priority system because there is actually one in browsers, which will make for instance UI tasks like dispatching click events have an higher priority than e.g network tasks caused by fetch. – Kaiido Jul 30 '22 at 15:54
  • @Kaiido That minimum timeout depends on the OS. On Windows it may be 1ms but on Linux it is typically 10ms. This is because it depends on the OS's tick/jiffy - the timer interrupt the OS uses to execute the OS instead of your programs. Linux has higher tick time because it is a server oriented OS that was designed to optimise throughput. Windows was designed to optimise GUI response (and gaming). The shorter the tick time the higher percentage of time the CPU is executing OS functions and that means the less CPU time your program gets. – slebetman Jul 30 '22 at 18:08
  • @Kaiido I wouldn't say it's wrong that "*the microtask queue has priority over the macrotask queue*". Sure, it's not a dynamic priority system, just statically assigned priority - but giving every microtask a higher priority than any macrotask has the same effect as doing a "microtask checkpoint" (processing the microtask queue until it is empty) after every macrotask. – Bergi Jul 30 '22 at 19:41
  • @David "*I've read the following three articles*" - I'm not sure what questions you have left then. How nodejs runs microtasks is explained in detail in them, no? – Bergi Jul 30 '22 at 19:59
  • No, the articles were about the microtask queue having priority over the macrotasks queue and how the event loop works in browsers. My question has not been answered. Please consider moving the discussion to the chat if it's not about the question as other users might think that I've got an answer. – David Jul 30 '22 at 20:02
  • @David I've written up an answer now which is hopefully satisfying, but I still think that this question (like your [previous one](https://stackoverflow.com/q/73114728/1048572)) is a duplicate of [In which phase of Nodejs event loop resolved promises' callbacks get executed?](https://stackoverflow.com/q/68613169/1048572). Let me know if there's anything left that is unclear – Bergi Jul 30 '22 at 20:33
  • @slebetman nope, not at all. Do you realize how long 10ms is? On a basic 60Hz monitor we have only 16.6 of these per frame. You imagine if 10ms were eaten just for ticking? The 1ms clamp is done here in node: https://github.com/nodejs/node/blob/574ad6d89dd9db2cb7a4b449118d4f8befab9b05/lib/internal/timers.js#L173 and here in Blink: https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/core/frame/dom_timer.cc#L125 but as I said they're working to remove it there: https://crbug.com/402694 – Kaiido Jul 31 '22 at 00:20
  • @Bergi, the point is that in a browser this priority is not given. It does matter because thinking they have priority means they're somehow treated the same. They aren't. This is important for instance with requestAnimationFrame callbacks (which aren't prioritized either), where you may have multiple such callbacks scheduled before the next rendering. Any task queued from there will happen after the rendering. Any microtask will happen before the next rAF callback. Microtasks are a special beast in the event loop and it's misleading to treat them as tasks. – Kaiido Jul 31 '22 at 00:27
  • @Kaiido Ah I had not considered rendering - is that treated like a task by the spec or directly built into the event loop algorithm? I still think it's possible to build a (working) conceptual model where microtasks are tasks with infinite priority (and possibly special handling of this priority level wrt rendering), but you're probably right that if the spec doesn't model it like that, we shouldn't either (and certainly not with the same terminology) to avoid confusing people. – Bergi Jul 31 '22 at 00:38
  • @Bergi the rendering is a step of the event loop, just like microtask checkpoints actually, and rAF callbacks execution. Only the first step *chooses* a task among task queues. This distinction is also becoming more important now because we're given the power to set tasks priority: https://wicg.github.io/scheduling-apis/ – Kaiido Jul 31 '22 at 01:36

1 Answers1

3

Where are the microtasks enqueue in the event loop in Node?

They can be enqueued anywhere - you probably meant to ask where they are dequeued :-)

The article you've read answers this:

The event loop should process the micro-task queue entirely, after processing one macro-task from the macro-task queue. […] But in NodeJS versions before v11.0.0, the micro-task queue is drained only in between each two phases of the event loop.

All those "phases" in your diagram are for processing macro tasks: timers, pending tasks, polled events, immediates, close events. The microtasks are executed after each of those macrotasks, just like in the browser.

The only exception weirdness is the special case of process.nextTick, which does not enqueue a macrotask either. This article distinguishes between a "promises microtask queue" and a "nextTick microtask queue" - the overarching term "microtask" being used for everything that comes before the next macrotask.

What phase has priority over what phase in the Node event loop?

There is no "priority". As the diagram shows nicely, they are executed one after the other, repeatedly, always in the same order, ad nauseam. Some phases have limits on the number of tasks to execute at once, to prevent starving the other phases, then the remaining tasks are run simply at the next time when it's the phase's turn again.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • " But in NodeJS versions before v11.0.0, the micro-task queue is drained only in between each two phases of the event loop." However, you've said that " The microtasks are executed after each of those macrotasks, just like in the browser.". So what is right ? Are microtasks dequeued after every single phase or after each 2 phases ? – David Jul 31 '22 at 08:25
  • 2
    The old (confusing, deprecated) behaviour was to run them after (all tasks in) every phase. The new (regular) behaviour is to run them after every task in every phase. "*So what is right?*" - it depends on your nodejs version! There is no "right" or "wrong". – Bergi Jul 31 '22 at 09:56