Let’s start with a common interview question:
console.log('start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
new Promise((resolve) => {
console.log('promise')
resolve()
})
.then(() => {
console.log('then1')
})
.then(() => {
console.log('then2')
})
console.log('end')
Copy the code
Many students should be able to answer the question. The result is:
start
promise
end
then1
then2
setTimeout
Copy the code
This involves macro and micro tasks in JavaScript event polling. So, can you clarify what macro and micro tasks are? Who initiated it? Why are microtasks executed before macro tasks?
First, we need to know how JS works.
JS runtime mechanism
Concept 1: JS is single-threaded execution
“JS is single threaded” refers to the JS engine thread.
In the browser environment, there are JS engine threads and rendering threads, and the two threads are mutually exclusive. In a Node environment, there are only JS threads.
Concept 2: Host
The environment in which JS runs. Usually a browser or Node.
Concept 3: Execution stack
Is a stack structure for storing function calls, following the principle of first in, last out.
function foo() {
throw new Error('error')
}
function bar() {
foo()
}
bar()
Copy the code
stack.jpg
When we start executing our JS code, we first execute a main function and then execute our code. According to the principle of “first in, last out”, the function that executes later is popped from the stack first. We can also see in the figure that foo executes later and is popped from the stack when it finishes executing.
Concept 4: Event Loop
How does JS actually work?
image
The JS engine resides in memory, waiting for JS code or functions to be passed to it by the host. That is, waiting for the host environment to assign macro tasks, and repeated waiting-execution is an event loop.
In the Event Loop, each Loop is called a TICK and performs the following tasks:
- The execution stack selects the first macro task (usually script) to be queued and executes its synchronized code until the end;
- Check whether there are microtasks, if there are, it will execute until the microtask queue is empty;
- If the host is a browser, the page might be rendered;
- Start the next tick, executing the asynchronous code in the macro task (callbacks such as setTimeout).
Concept 5: Macro and micro tasks
In the ES6 specification, microtasks are called Jobs and macroTasks are called Tasks. Macro tasks are initiated by the host, while microtasks are initiated by JavaScript itself.
In ES3 and previous versions, JavaScript itself does not have the ability to make asynchronous requests, and thus microtasks do not exist. After ES5, JavaScript introduced Promise so that the JavaScript engine itself could initiate asynchronous tasks without the need for a browser.
So, to sum up, the differences are as follows:
MacroTasks | Microtasks | |
---|---|---|
Who initiated | Host (Node, browser) | JS engine |
Specific events | SetTimeout /setInterval 3. UI rendering/UI events 4. PostMessage, MessageChannel 5. I/O (Node. Js) | 1. Promise 2. MutaionObserver 3. Object.Proxy Object substitution) 4. process.nexttick (node.js) |
Who first run | After running | First run |
Will a new Tick be triggered | will | Don’t |
Extension 1: How do async and await handle asynchronous tasks?
Simply put, async wraps asynchronous tasks with promises.
For example:
async function async1() { await async2() console.log('async1 end') } async function async2() { console.log('async2 end') } async1()Copy the code
Write ES5 instead:
new Promise((resolve, reject) => { // console.log('async2 end') async2() ... }).then(() => {console.log('async1 end')})Copy the code
When async1 is called, an async2 end is immediately printed and the function returns a Promise, which then causes the thread to start executing code other than async1 on hearing “await” (see “await” as a signal to give up the thread). Then when all the synchronous code is finished, all the asynchronous code is executed and goes back to the await position to execute the callback in the THEN.
Extension 2: setTimeout, setImmediate
SetImmediate and Process. nextTick are common methods in Node environments (IE11 supports setImmediate), so the rest of the analysis is based on the Node host.
Node.js is the JS running on the server side. Although it uses V8 engine, its API is somewhat different from native JS due to different service purposes and environments. Its Event Loop also needs to deal with some I/O, such as new network connections, so it is different from the browser Event Loop.
The execution sequence is as follows:
- Timers: Executes the callback of setTimeout and setInterval
- Pending Callbacks: I/O callbacks that are deferred until the next iteration of the loop
- Idle, prepare: used only in the system
- Poll: Retrieves new I/O events. Perform I/ O-related callbacks. In fact, almost all asynchrony is handled in this phase, except for a few other phases.
- Check: setImmediate Is performed here
- Close callbacks: some closed callbacks, such as socket.on(‘close’,…)
In general, setImmediate will perform before setTimeout, as follows:
console.log('outer');
setTimeout(() => {
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
}, 0);
Copy the code
Its execution sequence is:
- The outer layer is a setTimeout, so its callback is already in the Timers phase
- Handle the setTimeout inside because the callback is actually applied to the next timers phase because the timers are executing for this loop
- Handle setImmediate and add its callback to the check phase
- After the timers are displayed in the outer stage, the pending Callbacks, Idle, prepare, and poll queues are empty
- At the check phase, setImmediate’s callback is detected and taken out to execute
- Then close callbacks, queue is empty, skip
- Again, timers stage, execution
console.log('setTimeout')
However, this may not be true if the current execution environment is not the TIMERS phase… By the way, Node handles setTimeout differently: setTimeout(fn, 0) is forced to setTimeout(fn, 1).
Consider the following example:
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
Copy the code
Its execution sequence is:
- encounter
setTimeout
, but node.js forces it to be 1 ms, and inserts The Times phase - encounter
setImmediate
Insert the check phase - Synchronization code execution complete, enter
Event Loop
- Enter the first
times
Phase, check whether the current time has passed 1 ms. If yes, yessetTimeout
If not, the callback is skipped - Skip the empty phase and enter the check phase
setImmediate
The callback
So in the example above, setImmediate doesn’t necessarily perform before setTimeout.
Extension 3: Promise, process. NextTick Who executes first?
Because process.nextTick is the method in the Node environment, subsequent analysis is still Node based.
Process.nexttick () is a special asynchronous API that is not part of any Event Loop phase. In fact, when Node encounters this API, the Event Loop will not continue at all. It will immediately stop and execute process.nexttick (), and the Event Loop will continue after this execution.
Therefore, when nextTick and Promise appear at the same time, nextTick must execute first because the nextTick queue has a higher priority than the Promise queue.
Extension 4: Application Scenario – VM.$nextTick in Vue
Vm.$nextTick takes a callback function as an argument to defer the callback until after the next DOM update cycle.
The API is implemented based on event loops. The “next DOM update cycle” means updating the DOM the next time the microtask is executed, and vm.$nextTick adds a callback function to the microtask (which can be downgraded to a macro task in special cases).
Since microtasks are a high priority, Vue 2.4 has provided a way to enforce the use of macro tasks.
Vm.$nextTick creates microtasks in preference to promises. If promises are not supported or macro tasks are forced, then macro tasks are initiated in the following order:
- Preference detection for native setImmediate (a feature supported only by advanced VERSIONS of IE and Edge)
- If not, check to see if the native MessageChannel is supported
- If it is not supported, it is degraded to setTimeout.
summary
Here is an enhanced version of the exam that you can try.
console.log('script start') async function async1() { await async2() console.log('async1 end') } async function async2() { console.log('async2 end') } async1() setTimeout(function() { console.log('setTimeout') }, 0) new Promise(resolve => { console.log('Promise') resolve() }) .then(function() { console.log('promise1') }) .then(function() { console.log('promise2') }) console.log('script end')Copy the code