Javascript is a single-threaded, non-blocking scripting language. Single-threaded means that at any time javascript executes code, there is only one main thread to handle all tasks, and this is implemented through an event loop
Task queue
Function call stack
In the latest standard, they are called task and Jobs respectively. Their execution order is that the micro-task queue takes precedence over the >>> macro task queue, which is also known as the distributor. Their role is to distribute tasks to the appropriate queue
Macro-task includes:
Script setTimeout setInterval setImmediate I/O UI Rendering.
Micro-task includes:
Process. nextTick, Promise, Object.observe(deprecated), MutationObserver(new feature in HTML5)
Some of the APIS, such as Process. nextTick and setImmediate, do not exist in the browser but only in Node.js
Let’s go straight to the example
console.log('golb1');
setTimeout(function() {
console.log('timeout1');
process.nextTick(function() {
console.log('timeout1_nextTick');
})
new Promise(function(resolve) {
console.log('timeout1_promise');
resolve();
}).then(function() {
console.log('timeout1_then')
})
})
setImmediate(function() {
console.log('immediate1');
process.nextTick(function() {
console.log('immediate1_nextTick');
})
new Promise(function(resolve) {
console.log('immediate1_promise');
resolve();
}).then(function() {
console.log('immediate1_then')
})
})
process.nextTick(function() {
console.log('glob1_nextTick');
})
new Promise(function(resolve) {
console.log('glob1_promise');
resolve();
}).then(function() {
console.log('glob1_then')
})
setTimeout(function() {
console.log('timeout2');
process.nextTick(function() {
console.log('timeout2_nextTick');
})
new Promise(function(resolve) {
console.log('timeout2_promise');
resolve();
}).then(function() {
console.log('timeout2_then')
})
})
process.nextTick(function() {
console.log('glob2_nextTick');
})
new Promise(function(resolve) {
console.log('glob2_promise');
resolve();
}).then(function() {
console.log('glob2_then')
})
setImmediate(function() {
console.log('immediate2');
process.nextTick(function() {
console.log('immediate2_nextTick');
})
new Promise(function(resolve) {
console.log('immediate2_promise');
resolve();
}).then(function() {
console.log('immediate2_then')})})Copy the code
Step 1: The macro task script is executed first. Global push. Glob1 output.
Second, setTimeout is encountered during execution. SetTimeout acts as a task dispatcher to distribute tasks to the corresponding macro task queue. Note that setTimeout is executed directly on the function call stack, and its first argument, the deferred function, is distributed to the setTimeout queue
setTimeout(function() {
console.log('timeout1');
process.nextTick(function() {
console.log('timeout1_nextTick');
})
new Promise(function(resolve) {
console.log('timeout1_promise');
resolve();
}).then(function() {
console.log('timeout1_then')})})Copy the code
Step 3: Execution encounters setImmediate. SetImmediate is also a macro task dispenser that dispatches tasks to the corresponding task queue. SetImmediate’s task queue executes after the setTimeout queue.
setImmediate(function() {
console.log('immediate1');
process.nextTick(function() {
console.log('immediate1_nextTick');
})
new Promise(function(resolve) {
console.log('immediate1_promise');
resolve();
}).then(function() {
console.log('immediate1_then')})})Copy the code
Step 4: Execute when nextTick is encountered, process.nextTick is a microtask dispatcher that dispatches tasks to the corresponding microtask queue.
process.nextTick(function() {
console.log('glob1_nextTick');
})
Copy the code
Step 5: Implement meet Promise. Promise’s then method distributes the task to the corresponding microtask queue, but the methods in its constructor execute directly. Therefore, glob1_PROMISE will output a second output. If a Promise has a setTimeout in its construction option, it will be executed immediately, but its argument 1 (deferred execution) will be pushed into the setTimeout queue
new Promise(function(resolve) {
console.log('glob1_promise');
resolve();
}).then(function() {
console.log('glob1_then')})Copy the code
Step 6: Execution encounters a second setTimeout. It will come after the first setTimeout
setTimeout(function() {
console.log('timeout2');
process.nextTick(function() {
console.log('timeout2_nextTick');
})
new Promise(function(resolve) {
console.log('timeout2_promise');
resolve();
}).then(function() {
console.log('timeout2_then')})})Copy the code
Step 7: When nextTick and Promise are encountered, process.nextTick willglob2_nextTick
Push into the corresponding queue, and then the new Promise willconsole.log('glob2_promise')
Enter the function call stack and execute the exit, promise.then push into the Promise microtask queue
process.nextTick(function() {
console.log('glob2_nextTick');
})
new Promise(function(resolve) {
console.log('glob2_promise');
resolve();
}).then(function() {
console.log('glob2_then')})Copy the code
Step 8: Encounter setImmediate again
setImmediate(function() {
console.log('immediate2');
process.nextTick(function() {
console.log('immediate2_nextTick');
})
new Promise(function(resolve) {
console.log('immediate2_promise');
resolve();
}).then(function() {
console.log('immediate2_then')})})Copy the code
The nextTick queue is executed before Promie. After the executables in nextTick are complete, the tasks in the Promise queue will be executed.
It is important to note that when the current microtask queue is empty, it is called the end of an event loop
At this point, the second cycle starts, and a macro task is listed from setTimeout. It will repeat the above process and distribute the task to the corresponding queue. If its setTimeout has a nested setTimeout, it will be pushed to the bottom of the setTimeout queue. It will be executed later in the loop.
Async and await
The execution order of async and await in the task queue
Async is used to define an asynchronous function. Its asynchracy is determined by the await of the function. An await will return the following expression (either a promise object or a constant) as a promise object
The statement below await XXX is equivalent to XXX. Then (resolve)
- If XXX is a promise and the return value is a Thenable object (resove(),reject()), then the state of the awati promise will be determined by it. If reslove is fulfilled, that will be fulfilled. Then XXX. Then (resolve) will be executed, whereas the statement after awati will not be executed
- If XXX returns a null or a constant, such as console, await will process it with promise. Resolve (value) into a fulfilled promise object, which will be fulfilled later. And pass the value to the statement after await XXX, which is xxx.then(resolve(value))
Let’s do another example
async function async1() {
console.log( 'async1 start' )
await async2()
console.log( 'async1 end')}async function async2() {
console.log( 'async2' )
}
async1()
console.log( 'script start' )
// Output the result
async1 start
async2
script start
async1 end
Copy the code
-
First of all, async functions are called in the same way as synchronous functions, directly print async1 start
-
Then we get await async2()
Promise.resolve(asyncs()).then(console.log('async1 end')
Copy the code
At this time async2 will be called directly, because it is equivalent to the construction option in new Promise. Its return value is async2, which is encapsulated with await as a fulfilled promise. And its value will be transmitted to THEN, which will be pressed into the promise
-
Execute the script start
-
Execute async1 end in the promise.then queue
To sum up:
- Microtask queues take precedence over macro task queues
- The process.nextTick queue has the highest priority among microtask queues
- The promise’s construction options are executed directly
- Promise. then will be pushed into the promise queue
- SetTimeout is executed directly, and its delay function is pushed into the setTimeout queue
- Setinterval and setTimeout belong to the same queue
- An event loop ends when all microtask queues are empty