DOMContentLoaded event and task queue - javascript

I heard that there are three queues which have tasks in Event Loop Processing Model.
MacroTaskQueue : this queue have callback functions of setTimeout, setInterval ..etc
MicroTaskQueue : this queue have callback functions of promise, mutationOberver ..etc
AnimationFrameQueue : this queue have callback functions of requestAnimationFrame.
So, what i'm wondering is that
Who fires DOMContentLoaded event ?
Where the callback function of DOMContentLoaded is queued ? MacroTaskQueue or MicroTaskQueue?
finally,
var a = 10;
console.log(a);
setTimeout(function b() { console.log('im b'); }, 1000);
in this code,
var a = 10;
console.log(a);
is this code also queued in MacroTaskQueue or MicroTaskQueue ?
or only the b is queued in MacroTaskQueue after (min) 1000ms ?
Im in black hole. Help me please :D

What you call the "MacroTaskQueue" is actually made of several task-queues, where tasks are being queued. (Note that the specs only use multiple task-sources, there could actually be a single task-queue). At the beginning of the event-loop processing, the browser will choose from which task queue it will pick the next "main" task to execute. It's important to understand that these tasks may very well not imply any JavaScript execution at all, JS is only a small part of what a browser does.
The microtask-queue will be visited and emptied several times during a single event-loop iteration. For instance every time that the JS call stack has been emptied (i.e after almost every JS callback execution) and if it wasn't enough there are fixed "Perform a microtask checkpoint" points in the event-loop processing model.
While similar to a queue, the animation frame callbacks are actually stored in an ordered map, not in a queue per se, this allows to "queue" new callbacks from one of these callbacks without it being dequeued immediately after. More importantly, a lot of other callbacks are also executed at the same time, e.g the scroll events, resize events, Web animation steps + events, ResizeObserver callbacks, etc. But this "update the rendering" step happens only once in a while, generally at the monitor refresh rate.
But, that's not saying much about DOMContentLoaded.
Who fires DOMContentLoaded event ?
This event is fired as part of the Document parsing steps, in the "the end" section. The browser has to first queue a task on the DOM manipulation task-source. This task will then eventually get selected by the browser as part of the first step of the event-loop. And once this task's steps will be executed, the event will be fired and dispatched on the Document. That's only as part of this dispatch an event algorithm that the browser will invoke and inner-invoke until it calls our listener's callback.
Note that this Document parsing step is in itself quite interesting as a task since this is the most obvious place where you will have multiple microtask-checkpoints interleaved inside the "main" task (at each <script> for instance).
Where the callback function of DOMContentLoaded is queued ?
The callback function is not queued, it is conceptually stored in the EventTarget's event listener list. In the facts, it's stored in memory, since here the EventTarget is a DOM object (Document), it's probably attached to this DOM object, though this is an implementation detail on which the specs have little to say as this is transparent to us web-devs.
MacroTaskQueue or MicroTaskQueue?
As I hope you now understand better, neither. Task queues and the microtask-queue only store tasks and microtasks, not callbacks. The callbacks are stored elsewhere, depending on what kind of callbacks they are (e.g timers and events are stored in different "conceptual" places), and some task or microtask's steps will then call them.
is this code also queued in MacroTaskQueue or MicroTaskQueue?
That depends where this script has been parsed from. If it's inline in a classic <script> tag, then that would be the special parsing task we already talked about. If it's from a <script src="url.js">, then it will be part of a task queued from fetch a classic script, but it can also be part of a microtask, e.g if after an await in a module script, or you can even force it to be if you want:
queueMicrotask(() => {
console.log("in microtask");
eval(document.querySelector("[type=myscript]").textContent);
console.log("still in microtask");
});
console.log("in parsing task");
<script type="myscript">
var a = 10;
console.log(a);
setTimeout(function b() { console.log('im b'); }, 1000);
</script>
And it is even theoretically possible by specs that a microtask becomes a "macro-"task, though no browser does implements this anymore apparently.
All this to say, while I personally find all this stuff fascinating, as a web-dev you shouldn't block yourself on it.

Related

Click during a while loop is not placed in event loop

I am following https://www.youtube.com/watch?v=Bv_5Zv5c-Ts, and it is explained there that the event loop of JS engine places events in Event Queue - their handlers will be executed only when the Execution Stack is empty. The author even shows an example of it at 1:45:18. The code is:
function waitThreeSeconds() {
var ms = 3000 + new Date().getTime();
while(new Date() < ms) {}
console.log('finished function');
}
function clickHandler() {
console.log('click event!')
}
document.addEventListener('click', clickHandler);
waitThreeSeconds()
console.log('finished execution')
When I run it in the browser, the while loop runs for 3 seconds as expected, and then the 2 messages get printed:
If I click anywhere AFTER the while loop finishes, a message "click event!" gets printed. However, if I click DURING the while loop, the click event is never registered. In the video, in both situations, the click is registered.
Is it due to some updates in the JavaScript engines that happened since the video's premiere in 2015? I'm using the MS Edge browser.
The video's author suggests that even though JS is a single-threaded language, the web browsers implement JS interprets/engines in a concurrent way where the events get added to the Event Queue separately from the JS code's execution.
My experiment confused me since it shows different behavior. Could someone explain why is that?
//EDIT
After further experimentation, I found out that the behavior seen in the video is also to be found in Chromium (and I guess Chrome as well, although I do not have it installed).
Edge, however (the Chromium-based one), behaves differently and does not register the click at all during the while loop.
Web browsers can only operate asynchronously, not technically concurrently. What you've implemented here is what is traditionally called a "busy-wait" loop. This method is a blocking algorithm, meaning that the later code will not execute until the first code is done. This is a good thing though. Imagine how confusing it would be if your code just executed out of order.
If you want to utilize the browser's builtin asynchonous capabilities, you'll need to use one of the functions provided to interact with Javascript's Event Loop.
In this case, you would likely want to use setTimeout() to actually make this properly asynchronous.
function waitThreeSeconds() {
setTimeout(function() {
console.log('finished function');
}, 3000);
}
function clickHandler() {
console.log('click event!')
}
document.addEventListener('click', clickHandler);
waitThreeSeconds()
console.log('finished execution')
Other similar functions are setImmediate(), which executes as soon as the stack is empty, and setInterval(), which allows you to execute a function several times at a regular period or delay.
JavaScript doesn't run concurrently in a browser tab, so whenever you run a for/while loop the thread gets blocked and it must be complete to be able to handle other events in this case your event listeners.
So when you run the while loop no event will get processed until the loop finishes.
I tried running in chrome and it works the same way as it should both the listeners get fired after the loop.
The while loop is blocking, and will prevent the event queue from progressing. There is a similar question here! A good approach is to replace the while loop with a setTimeout(function(){},3000) or setInterval(function(){},3000)
If you use a while loop, you will be blocked from doing anything for those 3 seconds. Instead, you need to use timeout. This will allow you to continue to do things, while the timeout is running.
let activeTimer = null;
function waitThreeSeconds() {
if (activeTimer) {
console.log('stopped function execution');
clearTimeout(activeTimer);
}
console.log('began function execution');
activeTimer = setTimeout(function() {
console.log('finished function function');
activeTimer = null;
}, 3000);
}
function nonBlockingClickHandler() {
console.log(`Click event <${activeTimer != null}>!`);
}
document.addEventListener('click', nonBlockingClickHandler);
waitThreeSeconds();

How JavaScript DOM events are continuously handled even if they are inside a function

lets imagine a scenario
function clickEventCheck() {
document.querySelector('#some-id').addEventListener('click', () => {
console.log("The button is clicked");
});
}
clickEventCheck();
Now I get that for the first time the 'clickEventCheck' function is getting called and we can handle the click event.
But now the execution stack is empty so how the event handler line of code which is inside our function(which has returned) still gets executed every time we click the button?
When the function is called click listener is added to the element with id some-id. So, whenever you click the element, the code inside the callback function gets executed.
Until and unless the click listener is not explicitly removed from the element i:e element with id some-id, it will listen to click events.
function clickHandler() {
console.log('The button is clicked');
}
function clickEventCheck() {
document.querySelector('#some-id').addEventListener('click', clickHandler);
}
clickEventCheck();
document.querySelector('.remove').addEventListener('click', () => {
document.querySelector('#some-id').removeEventListener('click', clickHandler);
});
<button id="some-id">Click Me</button>
<button class="remove">Remove Click Listener</button>
JavaScript is doing more than just executing your code sequentially, line by line. In the background there is also something running called the event loop. This frequently checks another stack to see if any other instructions are there and if so runs those, starting additional chains of execution called frames.
Two nice properties of those frames is that they will keep running as long as they can so you don't have to reason about random switching between them, and they don't block, so once a frame can't execute any further, the engine will look for another frame to execute from the stack. This also leads to smooth switching between frames provided you are not doing CPU-heavy execution (which isn't normally what JavaScript's used for).
Practically what might happen is one frame (frame A) makes an I/O request, and provides a callback function when it does so. Frame A then runs out of code to execute and the event loop picks the next frame (frame B) to run. In the background, when the I/O request completes the callback function will be added to the stack—and then when frame B runs out of code, the event loop can pick up that callback and execute it in yet another frame. The same process could apply for a button click, a mouse move, or any asynchronous process you can get the computer to do.
In this way a large number of I/O connections can be smoothly handled simultaneously and this is a big selling point of NodeJs.
A lot of callbacks can get messy quickly and that's why there also exists concepts like Promises and async functions, which are topics for another day.

Why Node.js setImmediate executes after I/O callbacks?

As new member, I'm unable to comment on topics, that's why I had to create a new topic. But in this way, I can clarify the problem, so hopefully you guys can help me.
I have read quite a lot about Node.js Event Loop. And I have shaped my understanding of it based on following materials:
Node.js Event Loop
What the heck is the event loop anyway?
Why setImmediate() execute before fs.readFile() in Nodejs Event Loop's works?
(Please feel free to suggest other materials which are informative and accurate)
Especially the third link, has given me a better understanding. But keeping that in mind, I'm unable to understand Event Loop behavior for the following code:
var fs = require('fs');
var pos = 0;
fs.stat(__filename, function() {
console.log(++pos + " FIRST STAT");
});
fs.stat(__filename, function() {
console.log(++pos + " LAST STAT");
});
setImmediate(function() {
console.log(++pos + " IMMEDIATE")
})
console.log(++pos + "LOGGER");
Surprisingly, for me output is as follow:
LOGGER
FIRST STAT
LAST STAT
IMMEDIATE
screenshot of my terminal, showing output as well as node version
screenshot of output from online code compiler rextester.com
Keeping the Event Loop Diagram in mind, I guess flow should be as follow:
Interpretor firstly starts two stat operations.
Interpreter en-queues setImmedate callback (event) in the setImmedate queue
Call stack logs the logger
All event queues before I/O Poll phase are empty, so Event Loop(EL) moves on
In I/O Polling phase, EL collects the events and enqueues both the fs.stat callbacks in the "run completed I/O handlers" phase
EL checks the Check phase, and run the setImmediate callback
This round of EL ends, and second round starts
In "run completed I/O handlers", EL runs both callbacks (order of them can is onn-determinstic)
Question 1: Which part of my analysis/prediction is wrong?
Question 2: At which point, does Event Loop start working? Does it start from the beginning of the app (i.e. stage 1)? or does it start once the whole code is read by interpreter, all sync tasks are done within Call Stack, and Call Stack needs more task, i.e. between stage 3-4?
Thanks in advance,
setImmediate = execute without wait any I/O
In https://nodejs.org/docs/v8.9.3/api/timers.html#timers_setimmediate_callback_args says:
Schedules the "immediate" execution of the callback after I/O events' callbacks. Returns an Immediate for use with clearImmed
Steps:
callback for First stat is queued in I/O queue
callback for Last stat is queued in I/O queue
callback for immediate is queued in Immediates queue
LOGGER
If I/O operations (in 1 and 2) are finished the callbacks in 1 and/or 2 are marked as ready to execute
Execute the ready callbacks one by one (first timmer, then I/O, finally immediates). In your case:
First stat
Last stat
LOGGER
In the case that I/O does'nt ends at 5. then LOGGER were execute before FIRST STAT and LAST STAT.
See also: https://jsblog.insiderattack.net/timers-immediates-and-process-nexttick-nodejs-event-loop-part-2-2c53fd511bb3#f3dd

Very fast endless loop without blocking I/O

Is there a faster alternative to window.requestAnimationFrame() for endless loops that don't block I/O?
What I'm doing in the loop isn't related to animation so I don't care when the next frame is ready, and I have read that window.requestAnimationFrame() is capped by the monitor's refresh rate or at least waits until a frame can be drawn.
I have tried the following as well:
function myLoop() {
// stuff in loop
setTimeout(myLoop, 4);
}
(The 4 is because that is the minimum interval in setTimeout and smaller values will still default to 4.) However, I need better resolution than this.
Is there anything with even better performance out there?
I basically need a non-blocking version of while(true).
Two things that will run sooner than that setTimeout:
process.nextTick callbacks (NodeJS-specific):
The process.nextTick() method adds the callback to the "next tick queue". Once the current turn of the event loop turn runs to completion, all callbacks currently in the next tick queue will be called.
This is not a simple alias to setTimeout(fn, 0). It is much more efficient. It runs before any additional I/O events (including timers) fire in subsequent ticks of the event loop.
Promise settlement notifications
So those might be a tools for your toolbelt, doing a mix of one or both of those with setTimeout to achieve the balance you want.
Details:
As you probably know, a given JavaScript thread runs on the basis of a task queue (the spec calls it a job queue); and as you probably know, there's one main default UI thread in browsers and NodeJS runs a single thread.
But in fact, there are at least two task queues in modern implementations: The main one we all think of (where setTimeout and event handlers put their tasks), and the "microtask" queue where certain async operations are placed during the processing of a main task (or "macrotask"). Those microtasks are processed as soon as the macrotask completes, before the next macrotask in the main queue — even if that next macrotask was queued before the microtasks were.
nextTick callbacks and promise settlement notifications are both microtasks. So scheduling either schedules an async callback, but one which will happen before the next main task.
We can see that in the browser with setInterval and a promise resolution chain:
let counter = 0;
// setInterval schedules macrotasks
let timer = setInterval(() => {
$("#ticker").text(++counter);
}, 100);
// Interrupt it
$("#hog").on("click", function() {
let x = 300000;
// Queue a single microtask at the start
Promise.resolve().then(() => console.log(Date.now(), "Begin"));
// `next` schedules a 300k microtasks (promise settlement
// notifications), which jump ahead of the next task in the main
// task queue; then we add one at the end to say we're done
next().then(() => console.log(Date.now(), "End"));
function next() {
if (--x > 0) {
if (x === 150000) {
// In the middle; queue one in the middle
Promise.resolve().then(function() {
console.log(Date.now(), "Middle");
});
}
return Promise.resolve().then(next);
} else {
return 0;
}
}
});
$("#stop").on("click", function() {
clearInterval(timer);
});
<div id="ticker"> </div>
<div><input id="stop" type="button" value="Stop"></div>
<div><input id="hog" type="button" value="Hog"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
When you run that and click the Hog button, note how the counter display freezes, then keeps going again. That's because of the 300,000 microtasks that get scheduled ahead of it. Also note the timestamps on the three log messages we write (they don't appear in the snippet console until a macrotask displays them, but the timestamps show us when they were logged).
So basically, you could schedule a bunch of microtasks, and periodically let those run out and run the next macrotask.
Note: I've used setInterval for the browser example in the snippet, but setInterval, specifically, may not be a good choice for a similar experiment using NodeJS, as NodeJS's setInterval is a bit different from the one in browsers and has some surprising timing characteristics.
there are some libs that can work like cron task, e.g., https://www.npmjs.com/package/node-cron
i think that using cron should be easier, and more flexible.

Does Javascript event queue have priority?

These days, I have read some documents about setTimeout and setInterval. I have learned that the Javascript is a single thread which will only execute one piece of code per time. At the same time, if there is a event happens, it will be pushed into the event queue and block until appropriate time. I want to know, when many events are blocked waiting to execute at the same time. Do these events have different priorities, so the high priority event will execute before the low ones. Or just a FIFO queue.
setTimeout(fn1, 10);
$(document).click(fn2); //will be called at 6ms;
$.ajax({ajaxSuccess(fn3); //async request,it uses 7ms;})
for () {
//will run 18ms;
};
In the above code, the setTimeout fn1 will happen at 10 ms,click event handler fn2 will at 6ms, ajax callback fn3 will at 7ms, but all the three functions will be blocked until the for loop finish. At 18ms, the for loop finished, so what order does these functions will be invoked.(fn1,fn2,fn3) or (fn2,fn3,fn1)
Work scheduled for the main JavaScript thread is processed FIFO. This includes callbacks from various async tasks, such as setTimeout and ajax completions, and event handlers. The only exception is that in the main popular environments (browsers and Node), the resolution callback of a native Promise jumps the queue (more accurately, goes in a different higher-priority queue), see my answer here for details on that.
But leaving aside native promise resolution callbacks:
but all the three functions will be blocked until the for loop finish. At 18ms, the for loop finished, so what order does these functions will be invoked. (fn1,fn2,fn3) or (fn2,fn3,fn1)
The time you give setTimeout is approximate, because when that time comes due the JavaScript UI thread may be busy doing something else (as you know); there's also a minimum time required by the (newish) spec, but the degree to which it's enforced varies by implementation. Similarly, you can't guarantee that the click event will be queued at 6ms, or that the ajax completion will occur at exactly 7ms.
If that code started, and the browser did the 10ms precisely, and the click event was queued exactly 6ms in, and the ajax request completed at exactly 7ms, then the order would be: fn2 (the click handler), fn3 (the ajax completion), fn1 (the setTimeout), because that's the order in which they'd be queued.
But note that these are extremely tight timings. In practice, I expect the order the callbacks were queued would be effectively random, because the timing of the click would vary, the timing of the ajax would vary, etc.
I think this is a better example:
var start = +new Date();
// Queue a timed callback after 20ms
setTimeout(function() {
display("20ms callback");
}, 20);
// Queue a timed callback after 30ms
setTimeout(function() {
display("30ms callback");
}, 30);
// Queue a timed callback after 10ms
setTimeout(function() {
display("10ms callback");
}, 10);
// Busy-wait 40ms
display("Start of busy-wait");
var stop = +new Date() + 40;
while (+new Date() < stop) {
// Busy-wait
}
display("End of busy-wait");
function display(msg) {
var p = document.createElement('p');
var elapsed = String(+new Date() - start);
p.innerHTML = "+" + "00000".substr(elapsed.length - 5) + elapsed + ": " + msg;
document.body.appendChild(p);
}
The order of output will be the two loop messages followed by the 10ms callback, 20ms callback, and 30ms callback, because that's the order in which the callbacks are queued for servicing by the main JavaScript thread. For instance:
+00001: Start of busy-wait
+00041: End of busy-wait
+00043: 10ms callback
+00044: 20ms callback
+00044: 30ms callback
...where the + numbers indicate milliseconds since the script started.
Does javascript event queue have priority?
Sort of. The event loop is actually composed of one or more event queues. In each queue, events are handled in a FIFO order.
It's up to the browser to decide how many queues to have and what form of prioritisation to give them. There's no Javascript interface to individual event queues or to send events to a particular queue.
https://www.w3.org/TR/2014/REC-html5-20141028/webappapis.html#event-loops
Each task is defined as coming from a specific task source. All the tasks from one particular task source and destined to a particular event loop (e.g. the callbacks generated by timers of a Document, the events fired for mouse movements over that Document, the tasks queued for the parser of that Document) must always be added to the same task queue, but tasks from different task sources may be placed in different task queues.
For example, a user agent could have one task queue for mouse and key events (the user interaction task source), and another for everything else. The user agent could then give keyboard and mouse events preference over other tasks three quarters of the time, keeping the interface responsive but not starving other task queues, and never processing events from any one task source out of order.
FIFO. There really isn't anything more to say. You don't get to schedule it.
This can be a bit of a pain when you're looking at multiple timeout operations that could conceivably happen at the same time. That said, if you're using Asynchronous behaviors you shouldn't be depending on how they get scheduled.

Categories

Resources