1 the introduction

Understand the differences between these concepts in depth this week with Tasks, MicroTasks, Queues and Schedules.

First the conclusion:

  • Tasks are executed sequentially, and the browser may render between Tasks.
  • Microtasks are also executed sequentially, when:
    • After each callback if there is no js stack in execution.
    • After each task.

2 an overview

Event Loop

Before we get into these concepts, let’s introduce the Event Loop.

First of all, browsers are multi-threaded. Each JS script is executed in a single thread, and each thread has its own Event Loop. All browser Windows of the same origin share an Event Loop for communication.

The Event Loop will continuously execute all queued Tasks in a Loop. The browser will prioritize these Tasks and execute them according to their priorities, which will cause the execution order and call order of Tasks and Microtasks to be different.

Promise with setTimeout

Look at the output order of the following code:

console.log("script start");

setTimeout(function () {
  console.log("setTimeout");
}, 0);

Promise.resolve()
  .then(function () {
    console.log("promise1");
  })
  .then(function () {
    console.log("promise2");
  });

console.log("script end");
Copy the code

The correct answer is script start, script end, promise1, promise2, setTimeout, synchronous script execution has the highest priority in the thread, and then the promise task is stored in Microtasks, The setTimeout task is stored in Tasks, and Microtasks take precedence over Tasks.

Microtasks can be translated as Microtasks in Chinese. As long as Microtasks are inserted, the Microtasks queue will continue to execute until the end, and the Tasks will not be executed until the end.

Click bubble + Task

Chrome, Firefox, Safari, and Edge all work differently, but only Chrome works correctly! Here’s why Chrome is right:

<div class="outer">
  <div class="inner"></div>
</div>
Copy the code
// Let's get hold of those elements
var outer = document.querySelector(".outer");
var inner = document.querySelector(".inner");

// Let's listen for attribute changes on the
// outer element
new MutationObserver(function () {
  console.log("mutate");
}).observe(outer, {
  attributes: true});// Here's a click listener...
function onClick() {
  console.log("click");

  setTimeout(function () {
    console.log("timeout");
  }, 0);

  Promise.resolve().then(function () {
    console.log("promise");
  });

  outer.setAttribute("data-random".Math.random());
}

/ /... which we'll attach to both elements
inner.addEventListener("click", onClick);
outer.addEventListener("click", onClick);
Copy the code

After clicking on the inner block, the correct output order should be:

click
promise
mutate
click
promise
mutate
timeout
timeout
Copy the code

The logic is as follows:

  1. Click on the triggeronClickThe function is pushed.
  2. Executed immediatelyconsole.log('click')printclick.
  3. console.log('timeout')Stack the Tasks.
  4. console.log('promise')Into the stack microtasks.
  5. outer.setAttribute('data-random')Triggers the listenerMutationObserverInto the stack microtasks.
  6. onClickWhen the function completes, the thread call stack is empty and the microTasks queue is executed.
  7. printpromiseTo printmutate, microTasks are empty.
  8. Bubble mechanism is implemented, outer div is also triggeredonClickFunction, similarly, printpromiseTo printmutate.
  9. After that, execute Tasks and printtimeoutTo printtimeout.

Simulate clicking bubble + task

If you change the trigger onClick behavior from click to:

inner.click();
Copy the code

Would it have turned out differently? The answer is yes (when unit tests don’t match user behavior, and there is no solution for single tests). The results of the four browsers are completely different, but Chrome is still logically correct. Let’s take a look at Chrome’s results:

click
click
promise
mutate
promise
timeout
timeout
Copy the code

The logic is as follows:

  1. inner.click()The triggeronClickThe function is pushed.
  2. Executed immediatelyconsole.log('click')printclick.
  3. console.log('timeout')Stack the Tasks.
  4. console.log('promise')Into the stack microtasks.
  5. outer.setAttribute('data-random')Triggers the listenerMutationObserverInto the stack microtasks.
  6. Because the bubble is changed to js call stack execution, so the JS call stack is not finished at this time, microtasks will not be executed, but continue to execute the bubble, outer’sonClickThe function is pushed.
  7. Executed immediatelyconsole.log('click')printclick.
  8. console.log('timeout')Stack the Tasks.
  9. console.log('promise')Into the stack microtasks.
  10. MutationObserverI haven’t called it yet, so this timeouter.setAttribute('data-random')The changes in the
  11. After the js call stack is executed, microtasks are executed and printed in the order of loadingpromise.mutate.promise.
  12. After the microtasks task is executed, the system starts to print Taskstimeout.timeout.

3 intensive reading

Given how complex task scheduling is and how browsers implement it differently, there are two things I don’t recommend:

  1. Business logic “smarts” rely on subtle differences between MicroTasks and Tasks execution logic.
  2. Memorize the order of the calls.

Apart from the fact that business logic that relies on the order of calls is inherently difficult to maintain, the order of calls to tasks varies from browser to browser. This can be due to deviations from the W3C standard specification or bugs that make the logic that relies on it vulnerable.

Although the above two examples are very complicated, we do not need to use this example as a classic recitation, just remember the implementation logic mentioned at the beginning of the article to deduce:

  • Tasks are executed sequentially, and the browser may render between Tasks.
  • Microtasks are also executed sequentially, when:
    • After each callback if there is no js stack in execution.
    • After each task.

Remember that Promise is Microtasks, setTimeout is Tasks, and Microtasks will be executed only after the JS Event Loop is completed, i.e. there is no content in the call stack. Microtasks inserted during the execution of Microtasks are executed sequentially, while Microtasks inserted during execution Tasks are not executed until the call stack is complete.

All of the above refers to the priority of the immediate execution of an Event Loop, not to be confused with the execution delay.

The Event Loop of THE JS thread is regarded as a function, and the synchronization logic execution within the function has the highest priority. If Microtasks or Tasks are encountered, they will be recorded immediately. When an Event Loop is executed, Microtasks will be called immediately. It is possible to do some rendering after the Microtasks queue completes, and then consider executing the Tasks queue after these browser actions complete.

4 summarizes

Finally, do not rely on the order in which Microtasks and Tasks are executed, especially in declarative programming environments. We can treat Microtasks and Tasks as asynchronous content, and do not care about the order in which they are executed.

The discussion address is: Intensive Reading Tasks, MicroTasks, Queues and Schedules · Issue #264 · DT-fe /weekly

If you’d like to participate in the discussion, pleaseClick here to, with a new theme every week, released on weekends or Mondays. Front end Intensive Reading – Helps you filter the right content.

Pay attention to the front end of intensive reading wechat public account

Copyright Notice: Freely reproduced – Non-commercial – Non-derivative – Remain signed (Creative Commons 3.0 License)