prompt

Hopefully you know what an Event Loop is and know something about Web workers so you can absorb it more easily

What is a long mission

The W3C performance group defines tasks that take longer than 50ms to execute as long tasks

So how do we optimize for long tasks?

setTimeout

Js is a single-threaded language and its main purpose is to manipulate the DOM. Js execution is also very simple (executing from the top down), but there are also asynchronous methods in JS, such as XHR, setTimeout, and so on.

The function of setTimeout is to push the current task into the task queue, and judge whether the time set by setTimeout is up after the code execution of the same step of the main thread is completed. If so, the task will be pushed out of the queue for execution.

Here is a sample Event Loop diagram:

Because long tasks take a long time to execute, which will block the main thread and make users feel the page is stuck, we often use setTimeout to push long tasks into the task queue and deal with long tasks after the synchronous code is executed:

const t = setTimeout((a)= >{
    clearTimeout(t);
    / / long task
},0)
Copy the code

Pay attention to the point

SetTimeout can solve most of the problems of long tasks because there are not many scenarios for the front-end to process long tasks, which are generally handed to the front-end after being processed by the server. However, this method cannot be used for tasks that are too long. For example, if a task takes seconds, users will still feel that the page is stuck.

The following scenario also leads to Caton:

 #box {
        width: 100px;
        height: 100px;
        background: green;
      }
      
<div id="box"></div>Var start = null; var element = document.getElementById('box'); element.style.position = 'absolute'; function step(timestamp) { if (! start) start = timestamp; var progress = timestamp - start; element.style.left = Math.min(progress / 10, 200) + 'px'; if (progress< 2000) {
    window.requestAnimationFrame(step); }}window.requestAnimationFrame(step);


setTimeout(function() {
  // 500msThe long task ofvar now = performance.now();
  while (now + 500 > performance.now()) {}
}, 100);
Copy the code

In the above code, setTimeout is used to handle long tasks, but there is an animation effect that takes 2 seconds to run. When setTimeout is executed, the animation is still running, at this time, the long tasks in the queue are pushed out and executed, causing the main thread to block, and the animation appears to be stuck. At this time, it is better not to block the main thread. It is best to use a Web Worker (described below)

Using time sharding

Time sharding is not an API, but rather a technical solution that splits long tasks into smaller ones and gives control of the main thread between smaller tasks so that the UI doesn’t get stuck.

The core idea of React Fiber technology is time sharding. Vue 2.x also uses time sharding, but implements sharding on a unit basis. Vue 3 removes time sharding due to low revenue.

demo

To understand this, write a long task and block the main thread for 1 second:

const start = performance.now();
let count = 0;
while (performance.now() - start < 1000) {}
console.log('done! ');

Copy the code

We can encapsulate a TS method that splits the long task into smaller tasks:

ts(function* (){
    const start = performance.now();
    let count = 0;
    while (performance.now() - start < 1000) {
        yield;
    }
    console.log('done! '); }) ()Copy the code

Let’s take a look at the results:

As you can see from the diagram, a long task is split into smaller tasks, and control of the main thread is handed over in each small task interval so that the page does not lag

Time fragmentation method is implemented based on Generator function

Due to the execution nature of the Generator function, it is easy to use it to implement a time-fragmentation function:

function ts(gen) {
  if (typeof gen === 'function') gen = gen();
  if(! gen ||typeofgen.next ! = ='function') return;
  return function next() {
    const start = performance.now();
    const res = null;
    do {
      res = gen.next();
    } while(! res.done && performance.now() - start <25);
    if (res.done) return;
    setTimeout(next);
  };
}
Copy the code

In the code above, we do a do while: execute multiple tasks together if the current task execution time is less than 25ms, otherwise execute as one task until res.done = true.

The yield keyword is used to suspend the execution of a task and cede control of the main thread. Use setTimeout to put unfinished tasks back into the task queue for execution

Optimize with Web Worker

As mentioned at the beginning of this article, JS is single-threaded, but browsers are not. The Web Worker allows us to create other threads in the background that are independent of the main thread, so we can give the Web Worker long, laborious tasks.

use

Due to poor shared thread browser support, we will only cover dedicated threads in this chapter.

We create a folder and create the index.html and worker.js directories in it as follows:

.├ ─ index.html ├─ worker.jsCopy the code

Index.html code:

<input type="text" id="ipt" value="" />
<div id="result"></div>

<script>
    const ipt = document.querySelector('#ipt');
    const worker = new Worker('worker.js');
    
    ipt.onchange = function() {
      // Send messages via postMessage
      worker.postMessage({ number: this.value });
    };
    
    // Receive messages via onMessage
    worker.onmessage = function(e) {
      document.querySelector('#result').innerHTML = e.data;
    };
</script>
Copy the code

Worker. Js code:

// self is like window in the main thread
self.onmessage = function(e) {
  self.postMessage(e.data.number * 2);
};
Copy the code

conclusion

  • SetTimeout is sufficient for most scenarios, but be careful where you use it
  • Tasks that take longer to execute can be handled using time sharding or handed over to the Web Worker

reference

  • Use of Web workers
  • Time Slicing
  • Time Slicing
  • Worker
  • More on the Event Loop