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.
Related
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.
I have this piece of code:
// * Run this snippet of code multiple times
const fs = require('fs');
setTimeout(() => {
console.log('timer');
});
fs.readFile('', 'utf-8',(err, data) => {
console.log('io');
});
setImmediate(() => {
console.log('check');
});
On running the above mentioned code for multiple times. I'm getting different outputs.
Result 1
Somethimes I'm getting
timer
io
check
Result 2
and other times. I'm getting
io
check
timer
Can anyone please clarify what is going on here? I was expecting Result 1.
That has to do with how the event loop picks up things to do when more than one thing is available. Here you can find a good explanation.
If you really want to set that order, try async/await or calling fs.readFile() inside the setTimeout() callback.
Based on node.js event loop documentation, a setTimeout is called which sets a minimum amount of time to wait until the function is executed. Next you start an asynchronous file read. Then the polling phase of the event loop will begin. The polling phase has a queue of callback functions to complete. If the file read is not complete, it's callback function (the console.log) will not be put in the polling queue. The polling phase will instead wait for the timer to meet the minimum threshold time and then loop back to the timer callback execution phase. Since your setTimeout is set to zero ms, the polling phase will usually exit and complete the timer callback (console.log) first and then go back to polling for the file read to finish. This is why sometimes your setTimeout completes first if your underlying operating system delays the file read, or the file read completes first if the operating system delays the setTimeout. setImmediate will always happen immediately after the polling (io) phase.
I am studying about the event loop provided by libuv in Node. I came across the following blog by Deepal Jayasekara and also saw the explanations of Bert Belder and Daniel Khan on youtube.
There is one point that I am not clear with- As per my understanding, the event loop processes all the items of one phase before moving on to another. So if that is the case, I should be able to block the event loop if the setTimeout phase is constantly getting callbacks added to it.
However, when I tried to replicate that- it doesn't happen. The following is the code:
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('Hello World!');
console.log("Response sent");
res.end();
}).listen(8081);
setInterval(() => {
console.log("Entering for loop");
// Long running loop that allows more callbacks to get added to the setTimeout phase before this callback's processing completes
for (let i = 0; i < 7777777777; i++);
console.log("Exiting for loop");
}, 0);
The event loop seems to run in a round robin fashion. It first executes the callbacks that were added before i send a request to the server, then processes the request and then continues with the callbacks. It feels like a single queue is running.
From the little that I understood, there isn't a single queue and all the expired timer callbacks should get executed first before moving to the next phase. Hence the above snippet should not be able to return the Hello World response.
What could be the possible explanation for this?
Thanks.
If you look in libuv itself, you find that the operative part of running timers in the event loop is the function uv_run_timers().
void uv__run_timers(uv_loop_t* loop) {
struct heap_node* heap_node;
uv_timer_t* handle;
for (;;) {
heap_node = heap_min(timer_heap(loop));
if (heap_node == NULL)
break;
handle = container_of(heap_node, uv_timer_t, heap_node);
if (handle->timeout > loop->time)
break;
uv_timer_stop(handle);
uv_timer_again(handle);
handle->timer_cb(handle);
}
}
The way it works is the event loop sets a time mark at the current time and then it processes all timers that are due by that time one after another without updating the loop time. So, this will fire all the timers that are already past their time, but won't fire any new timers that come due while it's processing the ones that were already due.
This leads to a bit fairer scheduling as it runs all timers that are due, then goes and runs the rest of the types of events in the event loop, then comes back to do any more timers that are due again. This will NOT process any timers that are not due at the start of this event loop cycle, but come due while it's processing other timers. Thus, you see the behavior you asked about.
The above function is called from the main part of the event loop with this code:
int uv_run(uv_loop_t *loop, uv_run_mode mode) {
DWORD timeout;
int r;
int ran_pending;
r = uv__loop_alive(loop);
if (!r)
uv_update_time(loop);
while (r != 0 && loop->stop_flag == 0) {
uv_update_time(loop); <== establish loop time
uv__run_timers(loop); <== process only timers due by that loop time
ran_pending = uv_process_reqs(loop);
uv_idle_invoke(loop);
uv_prepare_invoke(loop);
.... more code here
}
Note the call to uv_update_time(loop) right before calling uv__run_timers(). That sets the timer that uv__run_timers() references. Here's the code for uv_update_time():
void uv_update_time(uv_loop_t* loop) {
uint64_t new_time = uv__hrtime(1000);
assert(new_time >= loop->time);
loop->time = new_time;
}
from the docs,
when the event loop enters a
given phase, it will perform any operations specific to that phase,
then execute callbacks in that phase's queue until the queue has been
exhausted or the maximum number of callbacks has executed. When the
queue has been exhausted or the callback limit is reached, the event
loop will move to the next phase, and so on.
Also from the docs,
When delay is larger than 2147483647 or less than 1, the delay will be set to 1
Now, when when you run your snippet following things happen,
script execution begins and callbacks are registered to specific phases. Also, as the docs suggests the setInterval delay is implicitly converted to 1 sec.
After 1 sec, your setInterval callback will be executed, it will block eventloop until all iterations and completed. Meanwhile, eventloop will not be notified of any incoming request atleast until loop terminates.
Once, all iterations are completed, and there is a timeout of 1 sec, the poll phase will execute your HTTP request callback, if any.
back to step 2.
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.
If the callback for a setTimeout invocation is added to the job queue (for example, if it is next on the job queue), and a clearTimeout is called on the current tick of the event loop, supplying the id of the original setTimeout invocation. Will the setTimeout callback on the job queue be run?
Or does the runtime magically remove the callback from the job queue?
No, it won't run; it will be queued and then subsequently aborted. The specificiation goes through a number of steps when you call setTimeout, one of which (after the minimum timeout, plus and user-agent padded timeouts etc) is eventually:
Queue the task task.
This appears to happen regardless of whether or not the handle that was returned in step 10 has been cleared - ie a call to setTimeout will always result in something being enqueued.
When you call clearTimeout, it:
must clear the entry identified as handle from the list of active timers
ie it doesn't directly affect the process already kicked off in the call to setTimeout. Note however that further up that process, task has been defined as:
Let task be a task that runs the following substeps:
If the entry for handle in the list of active timers has been cleared, then abort this task's substeps.
So when the task begins executing, it will first check if the handle has been cleared.
No, it won't be ran.
I don't know which source should be used to back it up officially, but it's at least easy to try for yourself.
function f() {
var t1 = setTimeout(function() { console.log("YES"); }, 2000);
sleep(3000);
clearTimeout(t1);
console.log("NO");
}