如果要弄懂promise,就必须弄懂什么是异步、什么是同步。
什么是同步呢
你可以理解为同一个时间,你只能干一件事1
2
3
4
5
6
7
8
9
10function second() {
console.log('second')
}
function first(){
console.log('first')
second()
console.log('Last')
}
first()
//first、second、last
调用栈
1 | 当执行此代码时,将创建一个全局执行上下文并将其推到调用堆栈的顶部;// 这个不太重要,下面是重点 |
什么是异步呢
1 | const getList = () => { |
消息队列
刚才我们说了,同步的时候,浏览器会维护一个‘执行栈’,除了执行栈,在开启多线程的时候,浏览器还会维护一个消息列表,除了主线程,其余的都是副线程,这些副线程合起来就叫消息列表。
增加难度:
1 | setTimeout(function() { |
事件轮询
上面我们说了,浏览器为了提升效率,为js开启了一个不太一样的多线程,因为js不能同时执行嘛,那副线程(注意是副线程里面哈)里面谁执行,这个选择的过程,就可以理解为事件轮询。我们先用事件轮询的顺序分析一下上面的代码,再来上概念:1
2
3
4
5
6promise函数肯定首先执行,他是主线程嘛,打印‘我是promise’;
然后继续走主线程,打印‘我是主线程’;
然后主线程走完了,开始走消息列表;
(宏任务和微任务一会再讲)
这个时候会先执行promise.then,因为他是微任务,里面的‘我是then!’
消息列表里面在上面的是定时器,但是定时器是宏任务,优先级比较低,所以会往后排;
什么是宏任务?微任务?
1 | 宏任务(Macrotasks):js同步执行的代码块,setTimeout、setInterval、XMLHttprequest、setImmediate、I/O、UI rendering等。 |
下面用代码来深入理解上面的机制:
1 | setTimeout(function() { |
这段代码作为宏任务,进入主线程。
先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。
接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue。
遇到console.log(),立即执行。
整体代码script作为第一个宏任务执行结束。查看当前有没有可执行的微任务,执行then的回调。 (第一轮事件循环结束了,我们开始第二轮循环。)
从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。 执行结果:1 - 2 - 3 - 4
1 | console.log('1') |
整体script作为第一个宏任务进入主线程,遇到console.log(1)输出1。
遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1。
遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1。
遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1。
又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2。
现在开始执行微任务,我们发现了process1和then1两个微任务,执行process1,输出6。执行then1,输出8。 第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮事件循环从setTimeout1宏任务开始:
首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。
new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2。
现在开始执行微任务,我们发现有process2和then2两个微任务可以执行输出3,5。 第二轮事件循环结束,第二轮输出2,4,3,5。第三轮事件循环从setTimeout2宏任务开始:
直接输出9,将process.nextTick()分发到微任务Event Queue中。记为process3。
直接执行new Promise,输出11。将then分发到微任务Event Queue中,记为then3。
执行两个微任务process3和then3。输出10。输出12。 第三轮事件循环结束,第三轮输出9,11,10,12。 整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。 (请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)