Promise是异步任务同步化的解决方案?本文讲通过以下几方面来介绍Promise:
Promise知识分五个章节来阐述,这篇我们讨论:Promise在Event Loop中执行顺序。
浅谈Event Loop
由于javaScript是单线程语言,所以我们要用到一个运行机制Event Loop,如果对Event Loop不了解,我会尽快出一篇文章,在我的”浏览器“的专栏中,希望关注阅读。
简单了解下EventLoop中的几个名词:
- 任务队列(task queue)
- 执行栈(调用栈)(execution context stack)
- 同步任务(synchronous)
- 异步任务(asynchronous)
- 宏任务(macrotask)
- 微任务(microtask)
- 宏任务队列(macrotask queue)
- 微任务队列(microtask queue) 做些预备工作,简单的说说Event Loop:
- Js解释器会先把代码浏览一遍,把同步任务放到执行栈中执行
- 异步任务的宏任务放到宏任务队列(这里的宏任务中可能包含微任务)中
- 微任务放入微任务队列中
- 当执行栈清空后,Js解释器先找微任务队列中的微任务,再放到执行栈中,
- 当执行栈又被清空且微任务队列没任务时,会查找宏任务队列,再执行宏任务中的同步任务, 循环往复下去,直至所有任务执行完毕。一次循环为一个tick。(至于执行栈是怎么工作,宏任务队列放到哪里挂载,宏任务微任务分别都有哪些 我会专门写一篇文章)。
Promise在Evnet Loop中的角色
我们这次主要针对Promise进行讲解,Promise设计初就是为了异步任务同步化,所以Promise内部执行是同步调用的,会立即执行executor
函数,then()
也是同步调用。而.then()
中的参数,是回调函数,是异步任务,异步任务中包含微任务,.then()
中的回调就是微任务的一员。
由于过于抽象, 我会多举些例子,以便固化总结其中规律,任务队列是先进先出,类似数组,我就当成一个数组来表示,更好理解。
明确了这一点,我们就可以从几个方面下手,看看每一种情况,执行到底是怎样的顺序。
1.基础执行顺序
1.Promise的executor
函数是怎么执行的。
console.log('script start');
new Promise((resolve, reject) => {
console.log('Promise1');
resolve()
})
console.log('script end');
// script start Promise1 script end
我们可以看出executor
函数在执行时,是同步调用的。没有进入任务队列中。
2.then()
是怎样调用的。
console.log('script start');
new Promise((resolve, reject) => {
console.log('Promise1');
resolve()
}).then(() =>console.log('then1'))
console.log('script end');
// script start Promise1 script end then1
第一轮
current task:script start
,promise1
,script end
micro task queue: [promise1的第一个then]
第二轮
执行 micro task queue第一个任务的同步任务
micro task queue: [promise1的第一个then]
current task:then1
可以看出then()
是一个异步任务的但属于微任务。接下来加大难度。
3.链式调用时的执行顺序
console.log('script start');
new Promise((resolve, reject) => {
console.log('Promise1');
resolve();
console.log('状态是resolved');
})
.then(() =>console.log('then1'))
.then(() =>console.log('then2'))
console.log('script end');
// script start Promise1 状态是resolved script end then1 then2
第一轮
current task:script start
,promise1
,状态是resolved
,script end
micro task queue: [promise1的then1]
第二轮
执行 promise1的第一个then
current task:then1
micro task queue: [promise1的then2]
第三轮
执行 promise1的第二个then
current task:then2
micro task queue: []
链式调用时 then()
每次生成一个新的promise,然后执行then()
,在执行的过程中不断向微任务队列中压入新的任务,因此直至微任务队列清空后才会执行下一波的微任务,继续加大难度。
4.如果分别调用呢
console.log('script start');
let p = new Promise((resolve, reject) => {
console.log('Promise1');
resolve()
})
p.then(() =>console.log('then1'))
p.then(() =>console.log('then2'))
console.log('script end');
// script start Promise1 script end then1 then2
其实执行顺序跟第三个是一样的,但是有个小知识点,涉及到源码。其实分别调用时.then
中的函数回调是push在一个数组中的。链式调用每个then()
返回一个新的Promise,每个then()
中的回调都有属于自己的微任务。分别调用时是批量执行的,不会被EventLoop其他函数插队。
5.executor中执行宏任务
console.log('script start');
let p = new Promise((resolve, reject) => {
setTimeout(()=>{
console.log('Promise1');
},1000)
resolve()
}).then(() =>console.log('then1'))
console.log('script end');
// script start script end then1 Promise1
第一轮
current task:script start
script end
micro task queue: [promise1的then1]
macro task queue: [promise1的setTimeout]
第二轮
current task:then1
micro task queue: []
macro task queue: [promise1的setTimeout]
第三轮
current task:Promise1
micro task queue: []
macro task queue: []
先执行微任务队列,当队列为空时,再执从宏任务队列找。
2.多次嵌套执行顺序
1.then()函数中执行Promise
console.log('script start');
new Promise((resolve,reject)=>{
console.log("promise1")
resolve()
}).then(()=>{
console.log("then1-1")
new Promise((resolve,reject)=>{
console.log("promise2")
resolve()
}).then(()=>{
console.log("then2-1")
}).then(()=>{
console.log("then2-2")
})
}).then(()=>{
console.log("then1-2")
})
console.log('script end');
//script start promise1 script end then1-1 promise2 then2-1 then1-2 then2-2
第一轮
current task:script start
,promise1
,script end
micro task queue: [promise1中的then1-1]
第二轮
current task:then1-1
,promise2
micro task queue: [promise2的then2-1,promise1的then1-2]
第三轮
current task:then2-1
,then1-2
micro task queue: [promise2的then2-2]
第四轮
current task:then2-2
micro task queue: []
再看一个例子,再总结规律。
2.then()函数中返回一个Promise
console.log('script start');
new Promise((resolve,reject)=>{
console.log("promise1")
resolve()
}).then(()=>{
console.log("then1-1")
return new Promise((resolve,reject)=>{
console.log("promise2")
resolve()
}).then(()=>{
console.log("then2-1")
}).then(()=>{
console.log("then2-2")
})
}).then(()=>{
console.log("then1-2")
})
console.log('script end');
//script start promise1 script end then1-1 promise2 then2-1 then2-2 then1-2
第一轮
current task:script start
,promise1
,script end
micro task queue: [promise1中的then1-1]
第二轮
current task:then1-1
,promise2
micro task queue: [promise2的then2-1]
第三轮
current task:then2-1
micro task queue: [promise2的then2-2,promise1的then1-2]
第四轮
current task: then2-2
,then1-2
micro task queue: []
通过这两个例子我们可以先确定:
1.Promise的then
无论连续调用几次,总是第一个then
进入微任务队列。
2.在then
中执行Promise,由于第一个规律,只向微任务队列压入当前promise2的第一个then
,继续向下执行,压入promise1的第二个then
。而then
中返回Promise的情况,由于是要整体返回一个内部的Promise,所以执行时,promise2的then
必须全部调用完毕,才能交给promise1的第二个then
使用。
再加点难度巩固一下。
3.then()函数中创建一个Promise并再new一个Promise
console.log('script start');
new Promise((resolve,reject)=>{
console.log("promise1")
resolve()
}).then(()=>{
console.log("then1-1")
new Promise((resolve,reject)=>{
console.log("promise2")
resolve()
}).then(()=>{
console.log("then2-1")
}).then(()=>{
console.log("then2-2")
})
}).then(()=>{
console.log("then1-2")
})
new Promise((resolve,reject)=>{
console.log("promise3")
resolve()
}).then(()=>{
console.log("then3-1")
})
console.log('script end');
//script start promise1 promise3 script end then1-1 promise2 then3-1 then2-1 then1-2 then2-2
第一轮
current task:script start
,promise1
,promise3
,script end
micro task queue: [promise1中的then1-1,promise3中的then3-1]
第二轮
current task:then1-1
,promise2
,then3-1
micro task queue: [promise2的then2-1,promise1的then1-2]
第三轮
current task:then2-1
,then1-2
micro task queue: [promise2的then2-2]
第四轮
current task: then2-2
micro task queue: []
最终执行顺序与我们总结规律是一致的,遇到promise,都会向微任务队列推入第一个then
,再执行微任务队列,直至清空队列,开启下一轮循环。再来一个变态的,巩固一下。
4.executor加入宏任务结合多种情况的.then
console.log('script start');
new Promise(resolve => {
setTimeout(()=>{
console.log('setTimeout');
new Promise(resolve => {
resolve();
}).then(() => console.log('setTimeoutthen'))
})
resolve();
}).then(() => {
//then1-1
new Promise(resolve => {
resolve();
})
.then(() => console.log('then2-1'))
.then(() => console.log('then2-2'));
}).then(() => {
//then1-2
new Promise(resolve => {
//promise3
resolve()
}).then(() => {
//then3-1
new Promise((resolve) => {
resolve()
}).then(() => console.log('then4-1'))
}).then(() => console.log('then3-2'))
}).then(() => console.log('then1-3'));
console.log('script end');
//script start script end then2-1 then2-2 then1-3 then4-1 then3-2 setTimeout setTimeoutthen
别慌,心中有两个任务队列,微任务队列和宏任务队列,一点点去分析。
第一轮
current task:script start
,script end
micro task queue: [Promise1的then1-1]
macro task queue: [setTimeout]
第二轮
current task:无输出
micro task queue: [Promise2的then2-1,Promise1的then1-2]
macro task queue: [setTimeout]
第三轮
current task:then2-1
micro task queue: [Promise2的then2-2,Promise3的then3-1,Promise1的then1-3]
macro task queue: [setTimeout]
第四轮
current task:then2-2
,then1-3
micro task queue: [Promise4的then4-1,Promise3的then3-2]
macro task queue: [setTimeout]
第五轮
current task:then4-1
,then3-2
micro task queue: []
macro task queue: [setTimeout]
第六轮
current task:setTimeout
micro task queue: []
macro task queue: [setTimeout中的then]
第七轮
current task:setTimeoutthen
micro task queue: []
macro task queue: []
通过这个例子我们可以看到,无论promise1,then()
内部怎么运转,在每次轮询中,都是会顺序的吧then1-1
,then1-2
,then1-3
依次压入微任务队列,then1,2内部的promise2,3,4也会根据then1,2的执行顺序开始。
留在最后 到这里基本Promise在EventLoop中执行的顺序就差不多了,但是还有一个关键的是
async/await
加上Promise,他们之间的顺序是怎样的?说async/await
又离不开generator
函数,这可能还要拿出一片文章去探讨。
这次文章引发了两个还需关注的点:
1.浏览器的EventLoop。
2.深入了解async/await
及generator
。
更多知识请关注我,等我更新这两篇文章。
如果此文章对您有帮助或启发,那便是我的荣幸