catalogue
- Event cycle mechanism
- Browser event loop
- What are the macro and micro tasks in the browser
- setImmediate/setTimeout
- MessageChannel
- then method of promise
- MutationObserver
- Detailed explanation of interview questions
- What are the macro and micro tasks in the browser
- Event loop of Node
Event cycle mechanism
First understand the task queue
- All synchronization tasks are executed on the main thread to form an execution stack
- In addition to the main thread, there is a task queue. As long as the asynchronous task has the running result, an event is placed in the task queue.
- Once all synchronization tasks in the execution stack are completed, the system will read the task queue to see what events are in it. Those corresponding asynchronous tasks end the waiting state, enter the execution stack and start execution.
- The main thread repeats the third step above.
At this time, it is divided into browser event loop and Node event loop. The following will be explained in detail.
Browser event loop
The main thread reads events from the task queue. This process is endless, so the whole operation mechanism is also called event loop
The above process is a macro representation. In fact, the callback queue task queue is divided into macro task macro task and micro task micro task.
In an event cycle, the following steps are performed:
- First, put the synchronous code into the execution stack for execution. If there is an asynchronous event, its return result will be put into a task queue. Task queue is divided into macro task queue and micro task queue.
- When the execution stack is empty, priority will be given to checking whether there are events in the micro task queue
- If it exists, execute the corresponding callback in the queue event in turn until the micro task queue is empty, and then enter the next step
- If not, skip to the next step
- Check whether there are events in the macro task queue
- If it exists, the corresponding callback of the event in the queue is put into the execution stack for execution
- If not, skip to the next step
- If there is asynchronous code in the execution stack, it will be put into the next task queue. Repeat the first three steps
From the above, we know that the focus is to give priority to micro tasks and then macro tasks after the execution stack is empty.
What are the macro and micro tasks in the browser
Students who have written vue should be familiar with a method this$ nextTick(() => { ... }), At this time, we are at a loss about the classification of macro tasks and micro tasks. Let's see how vue source code handles it.
The definition is very clear
- Macro task
- setImmediate
- MessageChannel
- setTimeout
- Micro task
- then method of promise
- MutationObserver (vue2.0 has abandoned this API)
setImmediate/setTimeout
setImmediate is unique to ie. we can test it in IE browser environment
setImmediate(() => { console.log('setImmediate') }) setTimeout(() => { console.log('setTimeout') }, 0) // If the delay is set to 0, there will actually be a delay of 4ms. // => setImmediate // setTimeout
MessageChannel
H5 API is not very compatible, Go to mdn View use
Then do the following tests
let channel = new MessageChannel() let port1 = channel.port1 let port2 = channel.port2 port1.postMessage('hello careteen ~') port2.onmessage = function (e) { console.log(e.data) } // => hello careteen ~
then method of promise
Promise.resolve(100).then(data => { console.log(data) }) // => 100
MutationObserver This is also an asynchronous method belonging to micro task. Go to mdn View use
This method is in vue1 0 is used, but it is abandoned after 2.0.
The usage scenario is to create multiple nodes for the page. Let us know when the nodes are created.
html
<div id="box"></div>
js
let observer = new MutationObserver(function() { console.log('dom The update is finished') console.log(box.children.length) }) // Monitor changes in offspring observer.observe(box, { childList: true }) for (let i = 0; i < 5; i++) { box.appendChild(document.createElement('sapn')) } for (let i = 0; i < 10; i++) { box.appendChild(document.createElement('p')) } // =>When the node is created // dom update completed // 15
Detailed explanation of interview questions
case1:
Promise.resolve().then(() => { console.log('then1') setTimeout(() => { console.log('timer1') }, 0) }) console.log('start') setTimeout(() => { console.log('timer2') Promise.resolve().then(() => { console.log('then2') }) }, 0) // => start -> then1 -> timer2 -> then2 -> timer1
According to the above execution process, we can know the result.
- First execute the synchronization code start,
- Put the then method of promise into the micro task queue,
- Then execute the synchronization code then1
- Put setTimeout into macro task queue
- The callback of setTimeout is put into the macro task queue
- Wait until setTimeout2 time is up
- Synchronization code timer2 for executing setTimeout2 callback
- Put the then method of promise into the micro task queue, and then execute then2
- When the time of setTimeout1 is up, execute the synchronization code inside
Event loop of Node
- The V8 engine parses JavaScript scripts.
- The parsed code calls the Node API.
- The Lib API library UV is responsible for the execution of the node. It assigns different tasks to different threads to form an Event Loop, which returns the execution results of tasks to the V8 engine in an asynchronous manner.
- The V8 engine returns the result to the user.
The above is just a macro description. Like the browser, the queue of asynchronous event methods is subdivided into several. go to Node official website Detailed description can be viewed
The following is an excerpt from Node's official website
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
From the above model, we can know the execution order of the mechanism:
External input data -- > poll -- > check -- > Close callback -- > timer -- > I / O callbacks -- > idle, prepare -- > poll
Several asynchronous queues subdivided:
- timers phase
- setTimeout method
- setInterval method
- pending callbacks phase
- I/O read and write operations. Such as FS Readfile() method
- idle, prepare phase
- For internal use only, we don't need to pay attention to it for the time being
- poll stage
- Retrieve new I/O events and execute I/O related callbacks (this is true for almost all callbacks except shutdown callbacks, timer scheduled callbacks and setimmediate()); Nodes will block here at the appropriate time.
- check phase
- The callback of setImmediate() will be executed at this stage
- close callbacks phase
- For example, socket on('close', ...) This callback of the close event
The event loop mechanism is different from that of the browser:
- First, in the poll phase
- First check whether there are events in the poll queue
- If yes, the callback will be executed in the order of first in first out
- If not, it will check whether there is a callback of setImmediate
- If there is such a callback, it will enter the following check phase to execute these callbacks
- At the same time, check whether there is an expired timer
- If so, put the callbacks of these expired timers into the timer queue according to the calling order, and then loop into the timer stage to execute the callbacks in the queue
- The judgment order of setImmediate and timer is not fixed, which is affected by the code running environment
- If the queues of setImmediate and timer are empty, the loop will stay in the poll stage until an I/O event returns. The loop will enter the I/O callback stage and immediately execute the callback of this event
- First check whether there are events in the poll queue
- check phase
- This stage is dedicated to the callback of setImmediate. When the poll stage enters the idle state and there is a callback in the setImmediate queue, the event loop enters this stage
- close phase
- When a socket connection or a handle is suddenly closed (for example, the socket.destroy() method is called), the close event will be sent to this stage to execute the callback. Otherwise, the event will use process The nexttick () method is sent out
- timer phase
- In this stage, all expired timers are added to the callback in the timer queue in a first in first out manner. A timer callback refers to a callback function set through the setTimeout or setInterval function
- I/O callback phase
- As mentioned above, this stage mainly performs the callback of most I/O events, including some callbacks for the operating system. For example, when a TCP connection generates an error, the system needs to execute a callback to get the error report.
- So repeated cycle
- Special: process Although the method nexttick () is not listed above, it is called first when each stage is completed and ready to enter the next stage
- Unlike the task of executing poll queue, this operation will not stop until the queue is cleared. In other words, incorrect use will cause the node to enter an endless loop until the memory leaks
Detailed explanation of interview questions
It's said that it's a big push, which seems very boring. Here are a few case s for in-depth understanding
case1:
Promise.resolve().then(() => { console.log('then1') setTimeout(() => { console.log('timer1') }, 0) }) setTimeout(() => { console.log('timer2') Promise.resolve().then(() => { console.log('then2') }) }, 0) // => then1 -> timer2 -> then2 -> timer1 // or then1 -> timer2 -> timer1 -> then2
There are two possibilities for the results of the above code. After then2 is executed, timer1 may not be up or it may be up, because if the second parameter of setTimeout method is set to 0, there will be a 4ms delay in actual execution.
case2:
setTimeout(() => { console.log('timeout') }, 0) setImmediate(() => { console.log('setImmediate') }) // => setImmediate -> timeout // or timeout -> setImmediate
Run more than once, and the running result is not certain, which depends on the environment in which the code is running. Various complexities in the operation and environment will lead to the random determination of the order of the two methods in the synchronization queue.
Let's take another example case3:
let fs = require('fs') fs.readFile('1.setImmediate.html', () => { setTimeout(() => { console.log('timeout') }, 0) setImmediate(() => { console.log('setImmediate') }) }) // => setImmediate -> timeout
Recalling the above-mentioned stages, in the callback of I/O events, the callback of setImmediate always takes precedence over the callback of setTimeout. Therefore, the order of returned results is fixed.
Links
Node series - next section Touch your hand and bring you a commonjs specification