This is the 9th day of my participation in the August Wen Challenge.More challenges in August

preface

Dismantling implementation Promise and its surrounding | August more challenges In this paper, a lot of talk about the task of macro and micro point and it is closely related and the event loop mechanism. This article will also explore the details of the event loop mechanism.

Single-threaded language

JavaScript is a single-threaded language, as we all know. So why is JavaScript a single-threaded language, and is it a bad idea to change to multithreaded at all?

Ruan Yifeng senior mentioned the reason for this: JavaScript has been single-threaded since its birth. The reason is probably not to make the browser too complex, since multiple threads share resources and potentially modify each other’s results, which would be too complex for a web scripting language. JavaScript is a single-threaded language. A simple scenario: If two threads are working on a DOM at the same time, one is modifying it, and the other is deleting it, which one is the baseline? To avoid this scenario, JS is single threaded.

The Web worker standard proposed by H5 allows JavaScript to create multiple threads, but the sub-threads are completely controlled by the main thread, so JavaScript itself is still a single thread.

So what are the problems with the JavaScript single-threaded language? Take a single threaded task 🌰 :

 let hello = 'hello'
 let world = 'world'
 console.log(hello + ', ' + world)
Copy the code

After compiling the above code, the JS engine will put all the task code into the main thread. When the main thread starts executing, the tasks will be executed from top to bottom in order until hello, world is printed, and the main thread will exit automatically. Everything is fine

But the reality is complex and brutal 🤦♀️, and it is impossible to follow a routine all the time. What can I do if subsequent tasks are blocked after a task is executed for a long time?

Execution stack and task queue

Single-threaded means that all tasks need to be queued. If the previous task takes too long to execute, the later task will have to wait, such as the IO thread (Ajax request data), to wait for the results to come out before executing. However, this wait is not necessary. We can suspend the waiting task and continue to execute the subsequent task. Therefore, tasks can be divided into two types: one is synchronous tasks; One is asynchronous tasks. Synchronization tasks: all tasks are executed on the main thread, and the execution stack is used to manage the progress of synchronization tasks. Asynchronous task: after the asynchronous operation is completed, the task queue is first entered. When the main thread execution stack is empty, the asynchronous task in the task queue is read.

function helloWorld() {
  console.log('inner function')
  setTimeout(function() {
    console.log('execute setTimeout')
  })
}
helloWorld()
console.log('outer function')
Copy the code

Use the Loupe tool to analyze whether the above code is what we say it is.

  1. helloWorldThe function enters the stack and executeshelloWorldThe code inside the function.
  2. Console. log(' inside function ')Go to the execution stack and printWithin the function.
  3. performsetTimeout, is a scheduled task that needs to be delayed, so it is suspended first, and then the anonymous function is queued and the rest of the code on the main thread continues to be executed.
  4. Console. log(' out of function ')Go to the execution stack and printOutside the function.
  5. After executing the main thread code, read the anonymous function in the task queue and printexecute setTimeout.

The code execution sequence fits perfectly with the previous conclusion

Event loop

It is called an event loop because the main thread reads events from the task queue in a continuous loop. To better understand the Event Loop, refer to Philip Roberts’ talk “Help, I’m Stuck in an Event-Loop”)

As shown above, when the main thread runs, it generates a heap and a stack, and the code in the stack calls WebAPIs, enqueuing the specified callback function or event when the trigger condition is met. When the code on the stack is finished, it loops through the events in the task queue, and so on.

One more information point to take away from the diagram is that there is not only one type of task in the task queue, it includes things like input events (mouse scroll, click), microtasks, file reads and writes, Websockets, timers, and so on. Input events, file reads and writes, and Websockets are all asynchronous requests that wait for the I/O device to complete. How does a timer specify that code should proceed after a specified time? What are microtasks?

The timer

Timers are mainly composed of setTimeout and setInterval, which are similar but differ in the number of times. The former is executed once, while the latter is executed repeatedly. Take setTimeout as an example. The basic usage is as follows.

function helloWorld() {
  console.log('hello world')}let timer = setTimeout(helloWorld, 1000)
Copy the code

Quite simply, the code above prints Hello World after 1000ms using setTimeout. I don’t know if you have any questions? As mentioned above, tasks pushed into the queue are read and executed sequentially, so how can the timer callback function be guaranteed to be called within the specified time? Looking through the materials, I found that there is a concept about designing delay queue in Chromium, and the tasks in delay queue are calculated according to the launch time and delay time. If the task expires, it completes the expired task and then loops again. When using timers, there are other precautions 💢 If the execution time of the main thread task is too long, the execution of the timer task will be affected.

function helloWorld() {
  console.log('hello world')}function main() {
  setTimeout(helloWorld, 0)
  for(let i = 0; i < 5000; i++) {
      console.log(i)
  }
}
main()
Copy the code

In the code above,setTimeoutThe function sets a zero-delay callback function, but the callback cannot be called until 5000 cycles have been executed. To viewPerformancePanel to performhelloWorldIt’s almost late400Ms, as shown in the following figure.

If the timer has nested calls, the system sets the minimum interval to4ms

function helloWorld() { setTimeout(helloWorld, 0)}
setTimeout(helloWorld, 0)
Copy the code

ChromeIf the timer is called more than 5 times nested, the current method is blocked if the interval is less than4ms, sets each interval to4Ms. As shown in the figure below.

If the page is not active, the minimum timer interval is 1000ms. The purpose is to optimize the load loss and reduce power consumption.

Chrome, Safari, and Firefox all use a 32-bit storage delay. Therefore, a maximum of 2^ 31-1 = 2147483647(ms) can be stored. 31 because the highest bit of binary is a sign bit, -1 because there is a 0.

Macro and micro tasks

Understand the micro task, that macro task also have to figure out is not ~. The following table shows the related technologies of macro task and micro task.

Macro task Micro tasks
setTimeout MutationObserver (it)
setInterval Process. NextTick (node)
I/O, event Promise.then/catch/finally
SetImmediate (node) queueMicrotask
Script (whole block of code)
requestAnimationFrame
PostMessage, MessageChannel

When do macro and micro tasks perform?

Macro task: A new task is added to the end of the task queue, and a callback function is executed when the loop executes the task. Microtask: The callback function is executed before the current macro task completes.

Execution timing can be seen: each macro task is associated with a microtask queue. The execution sequence can be obtained as follows: execute macro task first, and then execute the micro-task under the current macro task. If the micro-task generates a new micro-task, continue to execute the micro-task. After completing the micro-task, continue the event cycle of the next macro task.

Practice is the sole criterion for testing truth. Take a Promise’s 🌰

console.log('start')

setTimeout(function() {  / / macro task
  console.log('setTimeout')},0)

let p = new Promise((resolve, reject) = > {
   console.log('Initialize Promise')
   resolve()
}).then(function() {
   console.log('internal Promise1') Micro / / task
}).then(function() {
   console.log('internal Promise2') Micro / / task
})

p.then(function() {
  console.log('external Promise1') Micro / / task
})
console.log('end')
Copy the code

  1. scriptMacro task, start executing code, printstart.
  2. encountersetTimeoutMacro task, queued to wait for the next event loop.
  3. encounterPromiseExecute immediately, printInitialize the Promise.
  4. encounternew Promise().thenMicrotask, inscriptA microtask queue for a macro task waiting for the current macro task to complete.
  5. encounterp.thenMicrotask, inscriptA microtask queue for a macro task waiting for the current macro task to complete.
  6. printend, the currentscriptThe macro task is complete.
  7. View the currentscriptThe microtask queue of the macro task, the queue is not empty, and the current queue head is extractednew Promise().then, perform printingInternal Promise1Come across againthenMicrotask, then proceed to printInternal Promise2Execute complete, exit team.
  8. scriptThe microtask queue under the macro task is not emptyp.then, perform printingExternal Promise1And out of the team.
  9. scriptThe microtask queue under the macro task is empty, and the next macro task starts.
  10. Executing macro taskssetTimeoutprintsetTimeout. Check that the task queue is empty and the program ends.

reference

What is an Event Loop in JavaScript

Small tools

Turn video GIF