Related knowledge points
- Threads in JS
- EventLoop
- Macro and micro tasks
Threads in JS
Why is JS single threaded?
The single threaded JS feature is that only one task can be executed at a time.
This is due to some interaction with the user and operations related to manipulating the DOM and so on, so JS uses a single thread, otherwise using multiple threads would introduce complex synchronization issues. If it’s multithreaded, and one thread is modifying the DOM and the other thread is deleting it, which one will prevail?
If the execution synchronization problem, multithreading needs to lock, the execution of the task is very tedious.
Note: Although the HTML5 standard allows JavaScript scripts to create multiple threads, child threads are completely controlled by the main thread and cannot manipulate the DOM.
Problems with single threads
Since JavaScript is single-threaded, single-threaded means blocking until one task is complete before the next one can be executed. This will lead to the page stuck state, the page does not respond, affecting the user experience, so have to appear synchronous task and asynchronous task solutions.
- Synchronization task: A task that is queued to be executed on the main thread can be executed only after the previous task is completed.
- Asynchronous task: a task that does not enter the main thread but enters the “task queue” will enter the main thread only when the “task queue” notifies the main thread that an asynchronous task is ready for execution.
How does JS implement asynchronous programming?
In order to solve the above problems, asynchronous task execution appears, and its operation mechanism is as follows:
- All synchronization tasks are executed on the main thread, forming an execution stack;
- In addition to the main thread, there is a “task queue”. Whenever an asynchronous task has a result, an event is placed in the “task queue”;
- Once all synchronization tasks in the execution stack have been executed, the system reads the “task queue” to see what events are in it. Those corresponding asynchronous tasks then end the wait state, enter the execution stack, and start executing;
- The main thread repeats the third part above;
Note: When the main thread reads the task queue, the task queue is a “first in, last out” data structure, and the first events are read by the main thread first. When the stack is empty, the first event of the task queue enters the main thread. At the same time, if there is a timer, the main thread needs to check the time when the timer is executed.
EventLoop – an EventLoop mechanism
Before diving into the event loop, a few concepts need to be understood:
- Execution context (
Execution context
) - Execution stack (
Execution stack
) - Micro tasks (
micro-task
) - Macro task (
macro-task
)
Execution context
An execution context is an abstract concept that can be understood as an environment in which code is executed. JS execution context is divided into three types: global execution context, function execution context, Eval execution context.
- Global execution context: This is the default or base context, and any code that is not inside a function is in the global context. It does two things: it creates a global
Window
Object (browser case), and setthis
Is equal to the global object, which can be an externally loaded JS file or local<scripe></script>
The code in the tag. There can be only one global execution context in a program; - Function execution context: Each time a function is called, a new context is created for that function. Each function has its own execution context, but is created when the function is called. There can be any number of function execution contexts;
Eval
Function execution context: executes ineval
The code inside a function also has its own execution context;
Execution stack
The execution stack is the “stack” in our data structure. It has the characteristic of “first in, last out”. It is because of this characteristic that when our code is executing, it encounters an execution context and pushes it into the execution stack successively.
When the code is executed, the code in the execution context at the top of the stack is executed first. When the execution context code at the top of the stack is completed, it is removed from the stack and continues to execute the next execution context at the top of the stack.
function foo() {
console.log("a");
bar();
console.log("b");
}
function bar() {
console.log("c");
}
foo();
Copy the code
- Initialization state, execute stack task is empty;
foo
Function execution,foo
Enter execution stack, outputa
, hit the functionbar
;- then
bar
Then enter the execution stack and start executionbar
Function, outputc
; bar
After a function exits the stack, it continues to execute the function at the top of the stackfoo
, and finally outputb
;foo
Out of the stack, all tasks in the execution stack are completed;
Event loop mechanism
The illustration above explains:
- Synchronous and asynchronous tasks enter different execution “places”, synchronous to the main thread, asynchronous to the main thread
Event Table
And register the function; - When the assigned task is complete,
Event Table
I’m going to move this function inEvent Queue
; - When the code in the stack completes execution, the stack (
call stack
When the task in) is empty, the task queue (Event quene
) to execute the corresponding callback; - This loop forms the JS event loop mechanism (
Event Loop
);
Macro and micro tasks
Let’s look at the result of this code:
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 500);
Promise.resolve()
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("promise2");
});
console.log("script end");
Copy the code
The output is:
script start
script end
promise1
promise2
setTimeout
Copy the code
JS has two types of tasks: macrotask (macrotask) and microtask (microtask). In ECMAScript, microtask is called jobs, and macrotask can be called task.
Macro task (macrotask
)
The code executed by each execution stack is a macro task (including fetching an event callback from the event queue and placing it on the execution stack each time);
- Each macro task completes the task from start to finish and does nothing else;
- Browser to enable JS internal macro tasks with
DOM
Tasks can be executed in an orderly manner, the page will be re-rendered after one macro task is executed and before the next macro task is executed (macro task => Render => macro task =>…). ;
Micro tasks (microtask
)
A task that can be understood to be executed immediately after the execution of the current macro task;
- That is, after the current macro task task, before the next macro task, before rendering;
- So its response speed compared to
setTimeout
(because thesetTimeout
Is a macro task) is faster because there is no need to wait for rendering; - That is, after a macro task is executed, all microtasks generated during its execution are executed (before rendering);
What kind of scenarios will form macro and micro tasks?
macrotask
:- Main code block;
setTimeout
;setInterval
Etc. (as you can see, each event in the event queue is a macro task);
microtask
:process.nextTick
;Promise.then
;catch
;finally
And so on;
Note: In a Node environment, process.nextTick takes precedence over Promise, which means that the nextTickQueue portion of the microtask will be executed after the macro task ends, and then the Promise portion of the microtask will be executed.
Operation mechanism
Now that that’s clear, how does the event loop work? The sequence of tasks described below is based on the function call stack.
- First, the event loop mechanism is subordinate
script
The code inside the tag starts, as we mentioned above, with the wholescript
The tag is handled as a macro task; - During code execution, if you encounter a macro task such as:
setTimeout
, the current task will be distributed to the corresponding execution queue; - In the process of execution, if micro-tasks are encountered, such as:
Pomise
In creatingPromise
Instance object, the code executes sequentially, if executedThen,
Operation, the task will be distributed to the microtask queue; script
The code in the tag is executed, and the macro tasks and micro tasks involved in the execution process are also allocated to the corresponding queue;- At this point, the macro task is completed, and then the microtask queue to execute all the existing microtasks;
- After the execution of the micro task, the first round of message cycle is completed, and the page is rendered once;
- Then the second message loop begins, fetching tasks from the macro task queue for execution;
- If there are no more tasks in the two task queues, all tasks are completed.
Macro and micro tasks
The above also briefly introduced the concept of macro task and micro task, below we mainly through the code to explain, a little bit more examples, please be patient to finish reading ~~~
Subject to a
console.log("1");
setTimeout(() = > {
console.log("2");
}, 1000);
new Promise((resolve, reject) = > {
console.log("3");
resolve();
console.log("4");
}).then(() = > {
console.log("5");
});
console.log("6");
Copy the code
The output is:
1
3
4
6
5
2
Copy the code
- Initialization state, execution stack is empty;
- Executed first
<script>
Tag synchronization code, at this time the global code into the execution stack, synchronous sequential execution of the code, output 1; - Asynchronous code encountered during execution
setTimeout
(macro task), which is assigned to the macro task asynchronous queue; - The synchronization code continues and one is encountered
promise
Asynchronous code (microtask), but the code in the constructor is synchronous code, output 3, 4 in sequence, thenthen
Subsequent tasks are added to the microtask queue; - Finally, execute the synchronization code, output 6;
- because
script
The code inside is processed as a macro task, so this loop will process all asynchronous tasks in the microtask queue until all tasks in the microtask queue are executed. There is only one microtask in the microtask queue, so the output is 5. - At this time the page to a page rendering, rendering completed, for the next cycle;
- Retrieves a macro task from the macro task queue, the previous one
setTimeout
, finally output 2; - At this point, the task queue is empty, the execution stack is empty, and the whole program is executed.
Add macro tasks and micro tasks to the main thread
Execution order: Main thread => Micro tasks created on main thread => macro tasks created on main thread
console.log("start");
setTimeout(() = > {
console.log("setTimeout"); // Put the callback code into another macro task queue
}, 0);
new Promise((resolve, reject) = > {
for (let i = 0; i < 5; i++) {
console.log(i);
}
resolve();
}).then(() = > {
console.log("Promise instance callback executes successfully"); // Put the callback code into the microtask queue
});
console.log("end");
Copy the code
The output is:
start
0
1
2
3
4
5
end
PromiseThe instance callback successfully executedsetTimeout
Copy the code
Create a microtask in a microtask
Execution order: Main thread => Microtasks created on the main thread => microtasks created on the microtask => macro tasks created on the main thread
If a microtask is included in a microtask, it will be executed first, or before the macro task created on the main thread.
setTimeout(_= > console.log(4)); / / macro task
new Promise(resolve= > {
resolve();
console.log(1); / / synchronize
}).then(_= > {
console.log(3); Micro / / task
Promise.resolve()
.then(_= > {
console.log("before timeout"); // Create a microtask in the microtask, which is executed before the macro task
})
.then(_= > {
Promise.resolve().then(_= > {
console.log("also before timeout");
});
});
});
console.log(2);
Copy the code
The output is:
1
2
3
before timeout
also before timeout
4
Copy the code
Two microtasks are performed simultaneously:
setTimeout(_= > console.log(5)); / / macro task
new Promise(resolve= > {
resolve();
console.log(1); / / synchronize
})
.then(_= > {
console.log(3); Micro / / task
Promise.resolve()
.then(_= > {
console.log("before timeout"); // Create a microtask in the microtask, which is executed before the macro task
})
.then(_= > {
Promise.resolve().then(_= > {
console.log("also before timeout");
});
});
})
.then(_= > {
console.log(4); Micro / / task
});
console.log(2);
Copy the code
The output is:
1
2
3
before timeout
4
also before timeout
5
Copy the code
setTimeout(_= > console.log(5)); / / macro task
new Promise(resolve= > {
resolve();
console.log(1); / / synchronize
})
.then(_= > {
console.log(3); Micro / / task
Promise.resolve()
.then(_= > {
console.log("before timeout1"); // Create a microtask in the microtask, which is executed before the macro task
})
.then(_= > {
Promise.resolve().then(_= > {
console.log("also before timeout1");
});
});
})
.then(_= > {
console.log(4); Micro / / task
Promise.resolve()
.then(_= > {
console.log("before timeout2"); // Create a microtask in the microtask, which is executed before the macro task
})
.then(_= > {
Promise.resolve().then(_= > {
console.log("also before timeout2");
});
});
});
console.log(2);
Copy the code
The output is:
1
2
3
before timeout1
4
before timeout2
also before timeout1
also before timeout2
Copy the code
Create a microtask in a macro task
Execution order: Main thread => Macro task queue on the main thread => micro tasks created in the macro task queue
Create a microtask in a macro task, and after the current macro task is executed, the microtask contained in the macro task is executed.
setTimeout(() = > {
console.log("timer_1");
setTimeout(() = > {
console.log("timer_3");
}, 500);
new Promise(resolve= > {
resolve();
console.log("new promise");
}).then(() = > {
console.log("promise then");
});
}, 500);
setTimeout(() = > {
console.log("timer_2");
}, 500);
console.log("end");
Copy the code
The output is:
end
timer_1
new promise
promise then
timer_2
timer_3
Copy the code
Macro tasks created in microtasks
Execution order: Main thread => Micro tasks created on the main thread => macro tasks created on the main thread => macro tasks created on the micro task
There is only one asynchronous macro task queue. When a macro task is created in a micro task, it is appended to the asynchronous macro task queue (the same queue as the main thread).
new Promise(resolve= > {
console.log("new Promise(macro task 1)");
resolve();
}).then(() = > {
console.log("micro task 1");
setTimeout(() = > {
console.log("macro task 3");
}, 500);
});
setTimeout(() = > {
console.log("macro task 2");
}, 1000);
console.log("Task queue");
Copy the code
The output is:
new Promise(macro task 1)
Task queue
micro task 1
macro task 3
macro task 2
Copy the code
new Promise(resolve= > {
console.log("new Promise(macro task 1)");
resolve();
}).then(() = > {
console.log("micro task 1");
setTimeout(() = > {
console.log("macro task 3");
}, 1000);
});
setTimeout(() = > {
console.log("macro task 2");
}, 1000);
console.log("Task queue");
Copy the code
The output is:
new Promise(macro task 1)
Task queue
micro task 1
macro task 2
macro task 3
Copy the code
SetTimeout (() => {// macro task 2 console.log(‘macro task 2’); }, 1000) setTimeout(() => {// macro task 2 console.log(‘macro task 2’); }, 500) it will be executed before Macro Task 3 because the timer will be added to the event queue in milliseconds.
Comprehensive examples:
console.log("======== main task start ========");
new Promise(resolve= > {
console.log("create micro task 1");
resolve();
}).then(() = > {
console.log("micro task 1 callback");
setTimeout(() = > {
console.log("macro task 3 callback");
}, 0);
});
console.log("create macro task 2");
setTimeout(() = > {
console.log("macro task 2 callback");
new Promise(resolve= > {
console.log("create micro task 3");
resolve();
}).then(() = > {
console.log("micro task 3 callback");
});
console.log("create macro task 4");
setTimeout(() = > {
console.log("macro task 4 callback");
}, 0);
}, 0);
new Promise(resolve= > {
console.log("create micro task 2");
resolve();
}).then(() = > {
console.log("micro task 2 callback");
});
console.log("======== main task end ========");
Copy the code
The output is:
======== main task start ========
create micro task 1
create macro task 2
create micro task 2
======== main task end ========
micro task 1 callback
micro task 2 callback
macro task 2 callback
create micro task 3
create macro task 4
micro task 3 callback
macro task 3 callback
macro task 4 callback
Copy the code
Contains the async/await
Subject to a
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end"); Joins the microtask queue after the await statement
}
async function async2() {
console.log("async2");
}
async1();
new Promise(resolve= > {
console.log("create micro task");
resolve();
}).then(() = > {
console.log("micro task callback");
});
console.log("script start");
Copy the code
The output is:
async1 start
async2
create micro task
script start
async1 end
micro task callback
Copy the code
Topic 2
async function job1() {
console.log("a");
await job2();
console.log("b"); // Add to the microtask queue
}
async function job2() {
console.log("c");
}
setTimeout(function () {
new Promise(function (resolve) {
console.log("d");
resolve();
}).then(function () {
console.log("e");
});
console.log("f");
});
job1();
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("g");
});
console.log("h");
Copy the code
The output is:
a
c
h
b
g
d
f
e
Copy the code
The title three
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log("script end");
Copy the code
The output is:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
Copy the code
The title four
async function t1() {
console.log(1);
console.log(2);
new Promise(function (resolve) {
console.log("promise3");
resolve();
}).then(function () {
console.log("promise4"); // Microtask 1
});
await new Promise(function (resolve) {
console.log("b");
resolve();
}).then(function () {
console.log("t1p"); // Microtask 2
});
Add 1 to the microtask queue after the await statement
// Microtask 5
console.log(3);
console.log(4);
new Promise(function (resolve) {
console.log("promise5");
resolve();
}).then(function () {
console.log("promise6");
});
}
setTimeout(function () {
console.log("setTimeout");
}, 0);
async function t2() {
console.log(5);
console.log(6);
await Promise.resolve().then(() = > console.log("t2p")); // Microtask 4
Join the microtask queue 2 after the await statement
// Microtask 6
console.log(7);
console.log(8);
}
t1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2"); // Microtask 3
});
t2();
console.log("end");
Copy the code
The output is:
1
2
promise3
b
promise1
5
6
end
promise4
t1p
promise2
t2p
3
4
promise5
7
8
promise6
setTimeout
Copy the code
The code following the await statement must wait until the await statement has been executed (including the micro-task) before it can execute the subsequent code. In other words, all the code following the await statement will be added to the micro-task only after the await statement has been executed. Therefore, when an await promise is encountered, You must wait for the await promise function to complete before adding all the code following the await statement to the microtask.
So, while waiting to await promise.then microtask:
- Run other synchronization code;
- Wait until the synchronized code runs, and start running
await promise.then
Micro tasks; await promise.then
When the micro task is complete, put theawait
All code following the statement is added to the microtask column;
Await statements are synchronous and all the code following await statements is an asynchronous microtask.
Topic five
async function b() {
console.log("b");
await c();
// Join microtask queue 2
console.log("b1");
}
async function c() {
console.log("c");
await new Promise(function (resolve, reject) {
console.log("promise1");
resolve();
}).then(() = > {
console.log("promise1-1"); // Microtask 2
});
// Join microtask queue 1
setTimeout(() = > {
console.log("settimeout1");
});
console.log("c1");
}
new Promise(function (resolve, reject) {
console.log("promise2");
resolve();
console.log("promise2-1");
reject();
})
.then(() = > {
console.log("promise2-2"); // Microtask 1
setTimeout(() = > {
console.log("settimeout2");
new Promise(function (resolve, reject) {
resolve();
}).then(function () {
console.log("settimeout2-promise");
});
});
})
.catch(() = > {
console.log("promise-reject");
});
b();
console.log("200");
Copy the code
The output is:
promise2
promise2-1
b
c
promise1
200
promise2-2
promise1-1
c1
b1
settimeout2
settimeout2-promise
settimeout1
Copy the code
Execution steps:
- Executing from the top down,
new Promise
Function immediately executed => Print:promise2
resolve()
将promise2-2
In themicroTask queuesettimeout2
In theThe macroTask queue- Proceed further => Print:
promise2-1
- perform
b()
Function => Print:b
- perform
await c()
Function => Print:c
- Enter the
await c()
In the functionnew Promise
Function => Print:promise1
resolve()
将promise1-1
In themicroTask queuesettimeout1
In theThe macroTask queue- If no, go to Print:
200
- I’m not on a mission. GomicroPerform the first entry in the missionmicroAction to be performed => Print:
promise2-2
- Continue to performmicroActions to be performed in the sequence of tasks => Print:
promise1-1
- microIf no action is performed, continue the task
c()
The code to execute in the function => prints:c1
c()
After the command is executed, continueb()
The code to be executed in the function => print:b1
- At this point, execute immediatelymicroNo further action is requiredThe macroThe first task to be executed in the task queue => Print:
settimeout2
- The macroIn task one, execute
new Promise
functionresolve()
将settimeout2-promise
In themicroTask in progress, execute immediatelymicroTask => Print:settimeout2-promise
- Continue to performThe macroAction to be performed in a task => Print:
settimeout1
Conclusion:
- Microtask queue takes precedence over macro task queue.
- Macro tasks created on the microtask queue are added to the end of the current macro task queue.
- The microtasks created in the microtask queue are added to the end of the microtask queue.
- As long as there are tasks in the microtask queue, the macro task queue will only wait for the completion of the microtask queue.
- Only after it’s run
await
Statement, before theawait
All code following the statement is added to the microtask column; - In case of
await promise
, must waitawait promise
Only after the function is executedawait
All the code following the statement is added to the microtask;- Waiting for the
await Promise.then
Microtask:- Run other synchronization code;
- Wait until the synchronized code runs, and start running
await promise.then
Micro tasks; await promise.then
When the micro task is complete, put theawait
All code following the statement is added to the microtask column;
- Waiting for the