前言
本篇博客的由来是一个小伙伴的分享👇🏻
笔者原本对于promise原理蛮有自信的,但在难题怪题的折磨之下还是破防了……
本文带大家梳理一下promise的机制,以及一些诡异之处。
结论先行
下面列出promise体系的运行规则:
-
then
方法是同步执行的,但somePromise.then(callback)
中的callback
会作为微任务进行调度(加入微任务队列中 or 注册存储在 innerList 上) -
somePromise.then(callback)
方法在somePromise
状态为pendding
时,callback
既不会执行也不会加入微任务队列,而是存储在somePromise
内部一个注册队列(暂称为 innerList )中。 -
innerList 会在
Promise
对象状态变成fulfilled
的瞬间,将队列内的回调函数依次加入微任务队列。 -
then
传入的callback
如果没有写返回值,则默认返回状态为fulfilled
,参数为undefined
的Promise
对象;若写出返回值value
,则返回状态为fulfilled
, 参数为value
的promise
对象。somePromise.then(()=>{ console.log(something) // return undefined // return value })
-
⭐️ 最奇葩的规则:
resolve(Promise.resolve())
要消耗2个微任务。详细论证见知乎这篇文章,也可以看接下来的例2结合理解。
somePromise.then(()=>{ return Promise.resolve() // 在 then 中 return Promise.resolve() 等价于resolve(Promise.resolve())哦 }.then(()=>console.log('resolve'))
理解了这些,处理promise的难题怪题就万变不离其宗了~
例① 基础认知
new Promise((resolve, reject) => { // 称作:promise1
resolve();
})
.then(() => { console.log("log: 第一个then"); }) // 称作:then1
.then(() => { console.log("log: 第二个then"); }) // 称作:then2
看到这题你可能开始疑惑了,这么简单的题目还拿来做例题?
是的,大家都知道这道题的输出顺序会是
log: 第一个then
log: 第二个then
但具体是如何工作的,还真不一定所有人都了解。
- 使用表格记录执行过程,将所有同步代码作为宏任务执行
微任务队列 | 当前执行上下文 |
---|---|
[ ] | global上下文 |
-
遇到第一个then,由于Promise1的状态已经是fulfilled,直接将then1-callback加入微任务队列首部 | 微任务队列 | 当前执行上下文 | | --- | --- | | [ then1-callback ] | global上下文 |
-
遇到第二个then,由于then1-callback还未执行,then1-promise的状态仍旧为pendding。此时将then2-callback注册在then1-promise的innerList中。 | 微任务队列 | 当前执行上下文 |then1-promise-innerList | --- | --- | --- | | [ then1-callback ] | global上下文 | [ then2-callback ]
-
当前宏任务执行完毕,将微任务队列依次取出执行。 | 微任务队列 | 当前执行上下文 |then1-promise-innerList | --- | --- | --- | | [ ] | then1-callback | [ then2-callback ]
-
打印 "log: 第一个then" ,return undefined。then1-promise状态变成fulfilled,传值为undefined。此时立刻将then1-innerList依次取出加入微任务队列 | 微任务队列 | 当前执行上下文 | | --- | --- | | [ then2-callback ] | then1-callback
-
取出下一个微任务并执行,打印 "log: 第二个then" | 微任务队列 | 当前执行上下文 | | --- | --- | | [ ] | then2-callback
-
微任务队列清空,无新的宏任务,执行结束。
例② 1个resolve = 2个then ???
下面我们看一下这个令人咋舌的规则
new Promise(resolve => { // promise1
resolve(Promise.resolve())
}).then(() => {
console.log('resolved') // then0
})
Promise.resolve() // promise2
.then(() => { console.log('1') }) // then1
.then(() => { console.log('2') }) // then2
.then(() => { console.log('3') }) // then3
看到这段代码,先说出你的答案吧。
·
·
·
答案是:1, 2, resolved, 3
下面我们来复盘一下执行过程
-
初始化表格 | 微任务队列 | 当前执行上下文 | | --- | --- | | [ ] | global
-
resolve(Promise.resolve()),根据我们的规则,要消耗两个微任务:首先推入第一个微任务。 | 微任务队列 | 当前执行上下文 | | --- | --- | | [ promise1消耗的第一个微任务 ] | global
-
执行then0,由于promise1的状态还未fulfilled,所以将then0-callback存储在promise1的innerList中 | 微任务队列 | 当前执行上下文 | promise1-innerList | --- | --- | ---| | [ promise1消耗的第一个微任务 ] | global | [ then0-callback ]
-
执行then1、then2、then3。由于promise2已经fulfilled,所以then1-callback可以直接加入微任务队列。而then2-callback、then3-callback分别存储在then1-promise的innerList中。 | 微任务队列 | 当前执行上下文 | promise1-innerList | then1-promise-innerList | then2-promise-innerList | --- | --- | --- | --- | --- | | [ promise1消耗的第一个微任务, then1-callback ] | global | [ then0-callback ] | [ then2-callback ] | [ then3-callback ]
-
取出微任务“promise1消耗的第一个微任务”并执行,将“promise1消耗的第二个微任务”加入微任务队列。 | 微任务队列 | 当前执行上下文 | promise1-innerList | then1-promise-innerList | then2-promise-innerList | --- | --- | --- | --- | --- | | [ then1-callback, promise1消耗的第二个微任务 ] | global | [ then0-callback ] | [ then2-callback ] | [ then3-callback ]
-
取出微任务then1-callback,打印 1 ,then1-promise状态变成fulfilled。取出then1-promise-innerList中任务并加入微任务队列。 | 微任务队列 | 当前执行上下文 | promise1-innerList | then1-promise-innerList | then2-promise-innerList | --- | --- | --- | --- | --- | | [ promise1消耗的第二个微任务, then2-callback ] | then1-callback | [ then0-callback ] | [ ] | [ then3-callback ]
-
取出微任务“promise1消耗的第二个微任务”并执行。执行完毕后promise1的状态终于变成fulfilled了!取出promise1-innerList中任务并加入微任务队列 | 微任务队列 | 当前执行上下文 | promise1-innerList | then2-promise-innerList | --- | --- | --- | ---| | [ then2-callback, then0-callback ] | | [ ] | [ then3-callback ]
-
取出微任务then2-callback,打印 2 ,then2-promise状态变成fulfilled。取出then2-promise-innerList中任务并加入微任务队列
微任务队列 | 当前执行上下文 | then2-promise-innerList |
---|---|---|
[ then0-callback, then3-callback ] | then2-callback | [ ] |
-
取出微任务then0-callback,打印 resolved 。 | 微任务队列 | 当前执行上下文 | | --- | --- | | [ then3-callback ] | then0-callback
-
取出微任务then3-callback,打印 3 。 | 微任务队列 | 当前执行上下文 | | --- | --- | | [ ] | then3-callback
-
微任务队列清空,无新的宏任务,执行结束。
现在看看这道题,你会分析了吗?
new Promise(resolve => {
resolve();
})
.then(() => {
new Promise(resolve => {
resolve();
})
.then(() => {
console.log("log: 内部第一个then");
return Promise.resolve();
})
.then(() => console.log("log: 内部第二个then"));
})
.then(() => console.log("log: 外部第二个then"));
// log: 内部第一个then
// log: 外部第二个then
// log: 内部第二个then