2

I'm learning execution stack, task queue and event loop mechanism.

I continuously clicked the button until main thread is available (just before function a() is done) like below.
I thought click(UI) events and setTimeout uses the same queue, called Macrotask or Task Queue, so when I click the button between 1s and 3s, I thought the log of the task2 is printed between task1 and task2. But the result was not like that. Task2(click event) is always printed first and setTimeout events(task1,task3) are printed after click events.

So I'm wondering if click events are using different queue mechanism from setTimeout, or click event is prioritized over setTimeout.

thank you in advance for your help

Operation

  1. click the button (task2)
  2. --------1000ms setTimeout task1--------
  3. click the button (task2)
  4. --------3000ms setTimeout task3--------
  5. click the button (task2)
  6. --------6000ms main thread is now available--------

My Expectation Log Order

fn a done
task2 (click) done
task1 (setTimeout 1000ms) done
task2 (click) done
task3 (setTimeout 3000ms) done
task2 (click) done

Result Log Order

fn a done
task2 (click) done
task2 (click) done
task2 (click) done
task1 (setTimeout 1000ms) done
task3 (setTimeout 3000ms) done

Code

const btn = document.querySelector('button');
btn.addEventListener('click', function task2() {
  console.log('task2 (click) done');
});

function a() {
  setTimeout(function task1() { 
    console.log('task1 (setTimeout 1000ms) done');
  }, 1000);

  setTimeout(function task3() { 
    console.log('task3 (setTimeout 3000ms) done');
  }, 3000);

  // hold main thread for 6000ms(*1)
  const startTime = new Date();
  while (new Date() - startTime < 6000);

  console.log('fn a done');
}

a();
<button>button</button>
<script src="main.js"></script>
tono
  • 23
  • 2

2 Answers2

1

I thought click(UI) events and setTimeout uses the same queue

They don't.

UI events use the user interaction(UI) task source, which in most browsers has its own task-queue, setTimeout uses the timer task-source which also has its own task-queue in most browsers.

Although it is not required by specs, UI task source has one of the highest priority of all task sources in almost all browsers, and timers have one of the lowest.

How this prioritization works is that at the first step of the event-loop's processing model, the user-agent(UA) has to pick in one of its event-queues which task it will execute.

Note: What is colloquially called a "macro-task" is any task that is not a microtask.
The microtask queue is not a task queue and while a microtask can be chosen as the main task in the first step of the event loop processing, the microtask queue can't be prioritized because it has to be emptied synchronously at each microtask-checkpoints, which can happen several times during each event-loop iteration, and particularly after the chosen task gets executed.

So here, when the while loop is done blocking the event-loop, and the next iteration starts, the UA will have to choose from which task-queue it should pick the next task. It will see in its UI task queue that there are new events waiting, and execute them because they have an higher priority.
Then when they're all done being executed, it will choose the timers queue and execute them in the order of their scheduled time.

Note also that they have a starvation system which prevents an high priority task-queue to block other task-queues for too long.


Finally, I should mention there is a proposal to let us web-devs deal directly with all this prioritization thing: main thread scheduling.

Using this experimental feature, we could rewrite the snippet as

if( !("scheduler" in window) ) {
  console.error("Your browser doesn't support the postTask API");
  console.error("Try enabling the Experimental Web Platform features in chrome://flags");

}
else {
  scheduler.postTask(() => { 
    console.log('task1 (background) done');
  }, { priority: "background" } );

  scheduler.postTask(() => { 
    console.log('task2 (background) done');
  }, { priority: "background" } );

  // hold main thread for 6000ms(*1)
  const startTime = new Date();
  while (new Date() - startTime < 2000);
  scheduler.postTask(() => { 
    console.log('task3 (user-blocking) done');
  }, { priority: "user-blocking" } );

  console.log('synchronous done');
}

And see that the final task is executed first.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thank you! Your answer helps me a lot to understand task queue and event loop mechanism! – tono Nov 10 '20 at 08:06
  • @Kaiido Could you answer to the next question? You've written that `setTimeout uses the timer task-source` yes this speaks to us about special task queue for timers, so we see **task** definition for timer: https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers:concept-task-2. But what about events, where are the same steps of **event task**? – MaximPro Jan 26 '22 at 03:31
  • @MaximPro https://html.spec.whatwg.org/multipage/webappapis.html#user-interaction-task-source – Kaiido Jan 26 '22 at 05:40
  • @Kaiido but it is just description of event tasks. Have you seen my link related with step? I expected to see something like that. I mean we can easily to see creating task of setTimeout and I want to see like it with event. – MaximPro Jan 26 '22 at 06:34
  • The link I provided links to the [W3C UI Events](https://w3c.github.io/uievents/) specs which defines this. Unfortunately they don't go as far as defining how the task is scheduled no. – Kaiido Jan 26 '22 at 06:36
  • @Kaiido so specs don't provide places where they make tasks for events, right? – MaximPro Jan 26 '22 at 06:39
  • Depends on which events. For UI events no. For the ones defined by HTML yes, for instance the [HTMLImageElement load event](https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data) steps are well defined. – Kaiido Jan 26 '22 at 06:45
  • @Kaiido damn, I've looked through whole html spec and didn't find tasks for UI events. I looked for exactly them. Suddenly, maybe you know reason why spec doesn't describe tasks for UI events? – MaximPro Jan 26 '22 at 06:51
  • Because UI events are not specced by WHATWG HTML, [W3C UI Events](https://w3c.github.io/uievents/) does spec them. – Kaiido Jan 26 '22 at 07:05
  • @Kaiido but still, I don't see tasks for events in W3C UI Events. Are they not described too in W3C? – MaximPro Jan 26 '22 at 07:10
  • ... I already answered to that. "Unfortunately they don't go as far as defining how the task is scheduled no.", "For UI events no." – Kaiido Jan 26 '22 at 07:23
  • @Kaiido ok, ok, I got it. I needed to sure that I didn't miss anything – MaximPro Jan 26 '22 at 07:28
  • @Kaiido sorry to bother you, I am interested in prioritized of task queues. Ok we have [this](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model:implementation-defined). Maybe do you know a concrete order of concrete queues of task? – MaximPro Jan 28 '22 at 06:27
  • @MaximPro I don't really understand your question. What about you post a full question directly, with all your current understanding and what you really are trying to find out? That would be more appropriate that us continuing to talk here about something else than the content of this post. – Kaiido Jan 28 '22 at 06:31
  • @Kaiido could you checkout my [question](https://stackoverflow.com/questions/70900239/is-whether-exists-defined-order-queues-of-tasks) – MaximPro Jan 29 '22 at 11:39
0

Let's just take a look at what you are doing with the setTimeout and while loop.

If you run the code snippet you will see that the timestamp on both the 1 second and 3 seconds timeout is basically the same. This is because the while loop is blocking the main thread for 6 seconds until it is available to run the functions in the setTimeout.

The callbacks are not executed exactly after 1 second or 3 seconds but only when the main thread is free. setTimeout guarantees the callback function to be executed after a minimum of x milliseconds, which is >= 1 second for task 1 and >= 3 seconds for task 3.

function a() {
  setTimeout(function task1() { 
    console.log('task1 (setTimeout 1000ms) done ' + new Date());
  }, 1000);

  setTimeout(function task3() { 
    console.log('task3 (setTimeout 3000ms) done ' + new Date());
  }, 3000);

  // hold main thread for 6000ms(*1)
  const startTime = new Date();
  while (new Date() - startTime < 6000);

  console.log('fn a done');
}

a();
mylee
  • 1,293
  • 1
  • 9
  • 14