Event Loop
前言
如果不知道js中的event loop,在代码中出现setTimeout,Promise,甚至复杂的链式调用时,你写的代码在何时执行,执行顺序如何,你就会陷入混乱。
我的总结
在Js中有个StackQueue,在第一次执行主代码段时,console.log,setTimeout,Promise等会根据从上到下的顺序依次进入StackQueue,每次进入时会判断它是同步任务还是异步任务,若是同步任务那么立马执行,比如console.log,new Promise里面的代码,当然执行的是里面的同步任务。 若是异步任务,会根据其是macrotask,还是microtask来将其加入宏任务队列,和微任务队列。比如promise的then回调函数会被加入microtask队列,而setTimeout的执行代码段会被加入macrotask队列。
主代码段执行完毕后,会将微任务队列的代码全部执行完毕,若产生其他微任务也会加在队列末尾,这一个回合执行完毕。
接着执行宏任务队列最前面的一条任务,若在执行过程中产生微任务则加入微任务队列。
执行微任务队列,再执行一条宏任务,如此往复。 宏队列,macrotask,也叫tasks。 一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:
setTimeout setInterval setImmediate (Node独有) requestAnimationFrame (浏览器独有) I/O UI rendering (浏览器独有)
微队列,microtask,也叫jobs。 另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:
process.nextTick (Node独有) Promise Object.observe MutationObserver
(注:这里只针对浏览器和NodeJS)
举个栗子:
console.log(1); //同步任务
setTimeout(() => { //异步任务 callback1
console.log(2);
Promise.resolve().then(() => { //callback2
console.log(3);
});
});
new Promise((resolve, reject) => { //同步任务
console.log(4)
resolve(5)
}).then((data) => { //异步任务 microtask callback3
console.log(data);
Promise.resolve().then(() => { //异步任务 microtask callback4
console.log(6)
}).then(() => {
console.log(7) //callback6
setTimeout(() => { // 异步任务 macrotask
console.log(8) callback7
}, 0);
});
})
setTimeout(() => { //异步任务:macrotask
console.log(9); //callback5
})
console.log(10); //同步任务
具体过程如下:
- 首先执行主代码段,将事件依次加入队列
| StackQueue | 任务 |
|---|---|
| 1 | console |
| 2 | setTimeout |
| 3 | new Promise |
| 4 | setTimeout |
| 5 | console |
在这个任务栈中,同步任务依次执行,输出:1 4 10
在压栈的过程中,macrotask队列,microtask队列也有了内容。
| macrotask | 任务 |
|---|---|
| 1 | callback1 |
| 2 | callback5 |
| microtask | 任务 |
|---|---|
| 1 | callback3 |
先把微任务队列的任务清空: 输出:5
产生新的微任务,继续执行,输出:6
| microtask | 任务 |
|---|---|
| 1 | callback4 |
又产生新的微任务,继续执行,输出:7
| microtask | 任务 |
|---|---|
| 1 | callback6 |
执行栈压入:setTimeout 宏任务栈压入:
| macrotask | 任务 |
|---|---|
| 1 | callback1 |
| 2 | callback5 |
| 3 | callback7 |
此时,微任务队列清空,开始执行宏任务队列第一条任务callback1,输出:2,同时在微任务队列压入新的任务
| microtask | 任务 |
|---|---|
| 1 | callback2 |
执行微任务队列,输出:3 再次执行宏任务队列最顶端的任务callback5,输出:9 微任务队列为空,继续执行宏任务callback7,输出:8
所以这一段代码完整的输出顺序是:
1
4
10
---
5
6
7
---
2
3
---
9
8
这么一描述你明白了吗??