This question already has answers here:
SetTimeout And SetImmediate with console.log
(2 answers)
Closed last month.
This post was edited and submitted for review last month and failed to reopen the post:
Original close reason(s) were not resolved
Example
// test.js
setTimeout(() => console.log('hello'), 0)
setImmediate(() => console.log('world'))
just run node test.js by node v12.12.12 on Intel MacBook Pro.
sometimes the output is:
hello
world
sometimes is:
world
hello
My Understanding
v8 will run the code firstly, when it meets setTimeout, it adds callback to timer phase queue; when it meets setImmediate, it adds callback to check phase queue.Then,
event loop starts to work.
So, which phase does event loop starts ? Some people say Poll phase.If it's true, event loop finds poll phase queue is empty, but check phase queue is not, so console.log('world') should be executed, then event loop comes to next iteration and reach at timer phase, console.log('hello') will be executed.In a word, the output should be always:
world
hello
Actually it's not always like this.
I have read the doc from nodejs official website, but it does't tell me which phase will be entered firstly after executes the script.
And this question is not about the difference between setTimeout and setImmediate. It's all about the phase concept.
To resolve this problem, I have to read nodejs source code ? Read pretty difficult cpp code ? Oh not, it feels really crazy.
Any answers will be appreciated!
setImmediate is non-standard and not implemented in V8, so this isn't a V8 question.
https://nodejs.dev/en/learn/understanding-setimmediate/ says:
A setTimeout() callback with a 0ms delay is very similar to setImmediate(). The execution order will depend on various factors, but they will be both run in the next iteration of the event loop.
Node docs say for setImmediate:
When multiple calls to setImmediate() are made, the callback functions are queued for execution in the order in which they are created.
whereas for setTimeout:
Node.js makes no guarantees about the exact timing of when callbacks will fire, nor of their ordering.
All of which sounds like there isn't a simple answer, and your code probably shouldn't rely on a particular order, which implies that you don't really need to care about being able to predict the order :-)
Related
I'm learning the mechanism of Event-Loop in Node.js, and I'm doing some exercises, but have some confusions as explained bellow.
const fs = require("fs");
setTimeout(() => console.log("Timer 1"), 0);
setImmediate(() => console.log("Immediate 1"));
fs.readFile("test-file-with-1-million-lines.txt", () => {
console.log("I/O");
setTimeout(() => console.log("Timer 2"), 0);
setTimeout(() => console.log("Timer 3"), 3000);
setImmediate(() => console.log("Immediate 2"));
});
console.log("Hello");
I expected to see the following output:
Hello
Timer 1
Immediate 1
I/O
Timer 2
Immediate 2
Timer 3
but I get the following output:
Hello
Timer 1
Immediate 1
I/O
Immediate 2
Timer 2
Timer 3
Would you please clarify for me how are these lines executed step by step.
First off, I should mention that if you really want asynchronous operation A to be processed in a specific order with relation to asynchronous operation B, you should probably write your code such that it guarantees that without relying on the details of exactly what gets to run first. But, that said, I have run into issues where one type of asynchronous operation can "hog" the event loop and starve other types of events and it can be useful to understand what's really going on inside if/when that happens.
Broken down to its core, your question is really about why Immediate2 logs before Timer2 when scheduled from within an I/O callback, but not when called from top level code? Thus it is inconsistent.
This has to do with where the event loop is in its cycle through various checks it is doing when the setTimeout() and setImmediate() are called (when they are scheduled). It is somewhat explained here: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout.
If you look at this somewhat simplified diagram of the event loop (from the above article):
You can see that there are a number of different parts to the event loop cycle. setTimeout() is served by the "timers" block at the top of the diagram. setImmediate() is served in the "check" block near the bottom of the diagram. File I/O is served in the "poll" block in the middle.
So, if you schedule both a setImmediate(fn1) and a setTimeout(fn2, 0) from within a file I/O callback (which is your case for Intermediate2 and Timer2), then the event loop processing happens to be in the poll phase when these two are scheduled. So, the next phase of the event loop is the "check" phase and the setImmediate(fn1) gets processed. Then, after the "check" phase and the "close callbacks" phase, then it cycles back around to the "timers" phase and you get the setTimeout(fn2,0).
If, on the other hand, you call those same two setImmediate() and setTimeout() from code that runs from a different phase of the event loop, then the timer might get processed first before the setImmediate() - it will depend upon exactly where that code was executed from in the event loop cycle.
This structure of the event loop is why some people describe setImmediate() as "it runs right after I/O" because it's positioned in the loop to be processed right after the "poll" phase. If you are in the middle of processing some file I/O in an I/O callback and you want something to run as soon as the stack unwinds, you can use setImmediate() to accomplish that. It will always run after the current I/O callback finishes, but before timers.
Note: Missing from this simplified description is promises which have their own special treatment. Promises are considered microtasks and they have their own queue. They get to run a lot more often. Starting with node v11, they get to run in every phase of the event loop. So, if you have three pending timers that are ready to run and you get to the timer phase of the event loop and call the callback for the first pending timer and in that timer callback, you resolve a promise, then as soon as that timer callback returns back to the system, then it will serve that resolved promise. So, microtasks (such as promises and process.nextTick()) get served (if waiting to run) between every operation in the event loop, not just between phases of the event loop, but even between pending events in the same phase. You can read more about these specifics and the changes in node v11 here: New Changes to the Timers and Microtasks in Node v11.0.0 and above.
I believe this was done to improve the performance of promise-related code as promises became more of a central part of the nodejs architecture for asynchronous operations and there is also some standards-related work in this area too to make this consistent across different JS envrionments.
Here's another reference that covers part of this:
Nodejs Event Loop - interaction with top-level code
The reason for this output is the asynchronous nature of javascript.
You set the first 2 outputs in a sort of timeout with the execution time to be 0 this makes them still wait a tick.
Next you have the file read which takes a while to be finished and thus delays the execution of the functions in the callback
The first console.log within the callback is fired as soon as the callback is executed and the rest within the callback follows the first part of your code
Lastly you have the console.log at the bottom which gets executed at first because there is no delay for it and it does not need to wait till the next tick.
As some added help, check out this video.
https://youtu.be/cCOL7MC4Pl0
The presenter gives an amazing talk on the event loop. I think it is a great resource.
While this is particularly for the browser, many aspects are shared in Node.
I know messages come into call stack from the queue when call stack is empty. Wouldn't it be better though, if event loop could push messages from queue directly to call stack without waiting? What reasons are behind this behavior? If the event loop would push a message at an exact time we could always rely on function such as setTimeout etc.
setTimeout(() => console.log("I want to be logged for 10ms, but I will never be :("), 10);
// some blocking operations
for(let i = 0; i < 500000000; i++){
Math.random() * 2 + 2 - 3;
}
console.log("I'll be logged first lol");
It'll probably never be changed due to consistency reason but I'm still curious. Maybe I'm not seeing something, and there is the serious technical reason behind the concept of waiting for an empty stack. Do you have access to some articles about architectural decisions in JS, or maybe you know fundamental examples when this behavior is necessary? There are many articles about how JS works, but I couldn't find anything like "Why event loop works exactly that way". Any help would be greatly appreciated.
V8 developer here. This question seems to be based on a misunderstanding of what "the call stack" is: it is not a data structure that anyone can just push things onto. Instead, it is a term for the current state of things when a bunch of functions have called each other. The only way to "push" another function onto the call stack is when the currently executing function calls it. If the event system inserted random calls at random places into your functions, that would lead to a pretty weird programming model.
You could design a programming environment that's conceptually similar, but rather than pushing anything onto the call stack, what it would do is interrupt and suspend whatever is currently executing, and execute a setTimeout-scheduled function (or event handler, etc) instead, and resume previous execution afterwards. One issue you'd have to solve is: what if this repeats, i.e. what if the scheduled function is interrupted by another scheduled function which is interrupted by another scheduled function, and so on? What if a scheduled function takes forever to finish: when does the previously executing code get to make progress again? Also, while this can be done in a single-threaded world, getting random interruptions is concurrency (which from a consistency point of view is equivalent to parallelism/multi-threading), so you'd need synchronization primitives like locks (essentially, have a way for functions to say "don't interrupt this section" -- which in turn means you can't actually guarantee the accuracy of scheduling requests). Don't underestimate the complexity cost all this would impose on programmers: when writing code, they'd have to keep in mind that anything could get interrupted at any time, and on the flip side that any data that one function might want to process might not be ready yet because another function that produced it hasn't finished running yet.
So in short, JavaScript's event loop system is what it is because the language avoids concurrency, and randomly interrupting functions to execute others is concurrency, even on a single-threaded system.
Is there a set number of instructions statements that get processed before checking the event queue/per tick/per loop (ways of saying the same thing, I think?)
Is there a set number of instructions that get processed before checking the event queue/per tick/per loop (ways of saying the same thing, I think?)
No, there is not.
In the node.js architecture, when an event is pulled from the event queue, it's tied to a callback. The interpreter calls that callback and that callback runs to completion. Only when it returns and the stack is again empty does it check to see if there is another event in the event queue to run.
So, it has absolutely nothing to do with a number of instructions. node.js runs your Javascript as single-threaded so there is no time slicing between pieces of Javascript which it sounds like your question perhaps was anticipating. Once a callback is called that corresponds to an even in the event queue, that callback runs until it finishes and returns control back to the interpreter.
So, it goes like this:
Pull event from the event queue
Call the Javascript callback associated with that event
Javascript callback runs until completion and then returns from the callback
node.js internals check event queue for next event. If an event is there, go to step 1 and repeat
If no event is there, go to sleep until an event is placed into the event queue.
In reality, this is a bit of a simplification because there are several different types of event queues with a priority order for which one gets to go first, but this describes the general process as it relates to your question.
There is no set number of instructions that get processed before checking the event queue. Each message is run to completion. From the Mozilla documentation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop):
Each message is processed completely before any other message is processed. This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be pre-empted and will run entirely before any other code runs (and can modify data the function manipulates). This differs from C, for instance, where if a function runs in a thread, it may be stopped at any point by the runtime system to run some other code in another thread.
A downside of this model is that if a message takes too long to complete, the web application is unable to process user interactions like click or scroll. The browser mitigates this with the "a script is taking too long to run" dialog. A good practice to follow is to make message processing short and if possible cut down one message into several messages.
I am a bit confused about when the event loop spins in the browser.
The questions are:
Does a task and the pending microtasks, happen in the same loop iteration/turn/tick?
Which are the actual conditions that need to be met in order for the loop to turn?
Are these conditions the same in node.js event loop? - I don't know if this is a stupid question.
Let's say we have a webpage and in the front end we have JavaScript code that schedules a task and waits for a promise (which is a microtask). Is the execution of the promise considered to happen in the same turn of the event loop as the task, or in different iterations?
I currently assume that they all happen in the same iteration. Since if betting otherwise, in the case of microtasks executing while mid-task, that would mean that the task would require multiple loop iterations in order to fully complete. Which seems wired to me. Would it be correct to also say that the update rendering part, that may occur after each task, happens in the same loop turn?
Thank you in advance!
------------------------------------------------------------------------------------
I know I am supposed to add a comment, but it is going to be a long one, and I also need to write code, so I am editing the question and asking for clarification here.
#T.J. Crowder Thank you so much for your time and detailed explanation!
I had indeed misread "microtasks are processed after callbacks (as long as no other JavaScript is mid-execution)" in this great article and had gotten a bit confused.
I also had questions about the 4ms setTimout for which I couldn't find information about, so thanks for that info also.
One last thing, though... If we were to mark the loop ticks between the example code, where would we put them (assuming console.logs do not exist)?
Suppose we have a function named exampleCode, having the following body:
setTimeout(setTimeoutCallback, 0);
Promise.resolve().then(promiseCallback);
For the above code, my guess would be...
Just before executing exampleCode (macro)task:
first loop tick
setTimeoutCallback (macro)task scheduling
Promise.then microtask scheduling
promiseCallback execution
second loop tick
setTimeoutCallback execution
third loop tick
Or is there an additional loop tick between between Promise.then microtask scheduling and promiseCallback execution ?
Thank you in advance once again!
Does a task and the pending microtasks, happen in the same loop iteration/turn/tick?
The task occurs, then when it ends, any pending microtasks it scheduled are run.
Which are the actual conditions that need to be met in order for the loop to turn?
It's not clear what you mean by this. It may be easier to think in terms of jobs and a job queue (which is the ECMAScript spec's terminology): If there is a pending job and the thread servicing that queue is not doing something else, it picks up the job and runs it to completion.
Are these conditions the same in node.js event loop?
Close enough, yes.
Let's say we have a webpage and in the front end we have JavaScript code that schedules a task and waits for a promise (which is a microtask). Is the execution of the promise considered to happen in the same turn of the event loop as the task, or in different iterations?
In a browser (and in Node), it happens after the task completes, when the task's microtasks (if any) are run, before the next queued task/job gets picked up.
For instance:
// This code is run in a task/job
console.log("Scheduling (macro)task/job");
setTimeout(() => {
console.log("timeout callback ran");
}, 0);
console.log("Scheduling microtask/job");
Promise.resolve().then(() => {
console.log("promise then callback ran");
});
console.log("main task complete");
On a compliant browser (and Node), that will output:
Scheduling (macro)task/job
Scheduling microtask/job
main task complete
promise then callback ran
timeout callback ran
...because the microtask ran when the main task completed, before the next macrotask ran.
(Note that setTimeout(..., 0) will indeed schedule the timer to run immediately on compliant browsers provided it's not a nested timeout; more here. You'll see people saying there is no "setTimeout 0", but that's outdated information. It's only clamped to 4ms if the timer's nesting level is > 5.)
More to explore:
MDN Concurrency Model and Event Loop
ECMAScript Spec: Jobs and Job Queues
WHAT-WG "HTML5" Spec: Event Loops, Processing Model, Timers, and Timer Initialization Steps
Re the code and question in the edit/comment:
setTimeout(setTimeoutCallback, 0);
Promise.resolve().then(promiseCallback);
Your guess looks pretty good. Here's how I'd describe it:
Schedule the task to run that script
(When thread is next free)
Pick up the next task (from #1 above)
Run that task:
Create the task for the timer callback
In parallel, queue the task when the time comes
Queue a microtask for the promise then callback
End of task
Microtask check
Run the then callback
(When thread is next free)
Pick up the next task (the one for the timer callback)
Run the task
End of task
Microtask check (none to do in this case)
The processing model text explicitly calls out that the task ends prior to the microtask check, but I don't think that's observable in any real way.
Let's say I do this:
var timer = setTimeout(function() {
console.log("will this happen?");
}, 5000);
And then after just less than 5 seconds, another callback (from a network event in NodeJS for example) fires and clears it:
clearTimeout(timer);
Is there any possibility that the callback from the setTimeout call is already in the queue to be executed at this point, and if so will the clearTimeout be in time to stop it?
To clarify, I am talking about a situation where the setTimeout time actually expires and the interpreter starts the process of executing it, but the other callback is currently running so the message is added to the queue. It seems like one of those race condition type things that would be easy to not account for.
Even though Node is single thread, the race condition the question describes is possible.
It can happen because timers are triggered by native code (in lib_uv).
On top of that, Node groups timers with the same timeout value. As a result, if you schedule two timers with the same timeout within the same ms, they will be added to the event queue at once.
But rest assured node internally solves that for you. Quoting code from node 0.12.0:
timer.js > clearTimeout
exports.clearTimeout = function(timer) {
if (timer && (timer[kOnTimeout] || timer._onTimeout)) {
timer[kOnTimeout] = timer._onTimeout = null;
// ...
}
}
On clearing a timeout, Node internally removes the reference to the callback function. So even if the race condition happens, it can do no harm, because those timers will be skipped:
listOnTimeout
if (!first._onTimeout) continue;
Node.js executes in a single thread.
So there cannot be any race conditions and you can reliably cancel the timeout before it triggers.
See also a related discussion (in browsers).
I am talking about a situation where the setTimeout time actually expires and the interpreter starts the process of executing it
Without having looked at Node.js internals, I don't think this is possible. Everything is single-threaded, so the interpreter cannot be "in the process" of doing anything while your code is running.
Your code has to return control before the timeout can be triggered. If you put an infinite loop in your code, the whole system hangs. This is all "cooperative multitasking".
This behavior is defined in the HTML Standard, the fired task starts with:
If the entry for handle in the list of active timers has been cleared, then abort these steps.
Therefore even if the task has been queued already, it'll be aborted.
Whether this applies to Node.js, however, is debatable, as the documentation just states:
The timer functions within Node.js implement a similar API as the timers API provided by Web Browsers but use a different internal implementation that is built around the Node.js Event Loop.