Reputation: 91
code:
setTimeout(() => console.log(1), 10);
for (let i = 0; i < 3e9; i++) {}
console.log(0);
window.onclick = () => console.log('click');
When this script is run:
If I do NOT 'click' while synchronous code on line 2 is running console shows expected result:
0
1
If I 'click' while synchronous code on line 2 is running console shows unexpected result:
0
click
1
So I have 2 questions:
Upvotes: 6
Views: 998
Reputation: 136588
There are various task-queues, which will all have different priorities, set by the User-Agent (UA).
The first step of the Event Loop's processing model is to choose a task from any of these tasks-queues, that's where they can decide that even if a task actually has been queued after an other one, they can pick it, as long as they aren't queued on the same task-queue.
The timer task source is often one of the less prioritized ones, and the UI task source one of the most prioritized ones.
That's what you are experiencing here.
Note that there is a proposal for an API allowing us web-devs to have access to this prioritization system: https://github.com/WICG/main-thread-scheduling/blob/master/PrioritizedPostTask.md
setTimeout(() => console.log("timeout"), 0);
if( window.scheduler ) { // try to use the Prioritized postTask API
scheduler.postTask(() => console.log("low priority task"), { priority: "background" });
scheduler.postTask(() => console.log("normal priority task"), { priority: "user-visible" });
scheduler.postTask(() => console.log("high priority task"), { priority: "user-blocking" });
}
else {
console.log( "The Prioritized postTask API can be enabled from chrome://flags/#enable-experimental-web-platform-features" );
}
// queue on the message task-source (faster than timeout)
const { port1, port2 } = new MessageChannel();
port1.onmessage = () => console.log("message");
port2.postMessage("");
// block for 3 full seconds
const start = performance.now();
while( performance.now() - start < 3000 ) {}
console.log(0);
In Chrome with the chrome://flags/#enable-experimental-web-platform-features
flag set on, this results in
0
high priority task
normal priority task
message
timeout
low priority task
To be 100% honest, the task prioritization may not be what makes the message event and the "normal" task fire before the "timeout" in my snippet. The timer-initialisation-steps algorithm actually is itself asynchronous and asks the UAs to queue that task while in parallel (step 14). So this task should anyway be queued after other tasks that are queued synchronously.
Note also that most UAs (at least Chromium and Firefox) have a starvation system in place, avoiding a queue to eat all the resources without letting the other queues to ever execute their tasks.
Also, to answer the title of the question, an Event is not a task, you can very well fire an Event synchronously, from the same task (e.g using EventTarget.dispatchEvent
).
And for the second bullet question,
"If I click when synchronous code from line 2 is running, why I see 'click' in console at all? I clicked before executor reached the line 4..."
that's simply because the UA will proceed the JavaScript job until its end before even queuing the task that will dispatch the Event to all its targets and finally call all their callbacks. So at that time, your event handler is well defined, as is its callback.
Upvotes: 9