「promise」 难题怪题一把梭哈

633 阅读5分钟

前言

本篇博客的由来是一个小伙伴的分享👇🏻

IMG_2282.jpg

原文出自:juejin.cn/post/684490…

笔者原本对于promise原理蛮有自信的,但在难题怪题的折磨之下还是破防了……
本文带大家梳理一下promise的机制,以及一些诡异之处。

结论先行

下面列出promise体系的运行规则:

  1. then 方法是同步执行的,但 somePromise.then(callback) 中的 callback 会作为微任务进行调度(加入微任务队列中 or 注册存储在 innerList 上)

  2. somePromise.then(callback) 方法在 somePromise 状态为 pendding 时,callback 既不会执行也不会加入微任务队列,而是存储在 somePromise 内部一个注册队列(暂称为 innerList )中。

  3. innerList 会在 Promise 对象状态变成fulfilled 的瞬间,将队列内的回调函数依次加入微任务队列。

  4. then 传入的 callback 如果没有写返回值,则默认返回状态为 fulfilled ,参数为 undefinedPromise 对象;若写出返回值 value,则返回状态为 fulfilled, 参数为 valuepromise 对象。

    somePromise.then(()=>{
        console.log(something)
        // return undefined
        // return value
    })
    
  5. ⭐️ 最奇葩的规则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

但具体是如何工作的,还真不一定所有人都了解。

  1. 使用表格记录执行过程,将所有同步代码作为宏任务执行
微任务队列当前执行上下文
[ ]global上下文
  1. 遇到第一个then,由于Promise1的状态已经是fulfilled,直接将then1-callback加入微任务队列首部 | 微任务队列 | 当前执行上下文 | | --- | --- | | [ then1-callback ] | global上下文 |

  2. 遇到第二个then,由于then1-callback还未执行,then1-promise的状态仍旧为pendding。此时将then2-callback注册在then1-promise的innerList中。 | 微任务队列 | 当前执行上下文 |then1-promise-innerList | --- | --- | --- | | [ then1-callback ] | global上下文 | [ then2-callback ]

  3. 当前宏任务执行完毕,将微任务队列依次取出执行。 | 微任务队列 | 当前执行上下文 |then1-promise-innerList | --- | --- | --- | | [ ] | then1-callback | [ then2-callback ]

  4. 打印 "log: 第一个then" ,return undefined。then1-promise状态变成fulfilled,传值为undefined。此时立刻将then1-innerList依次取出加入微任务队列 | 微任务队列 | 当前执行上下文 | | --- | --- | | [ then2-callback ] | then1-callback

  5. 取出下一个微任务并执行,打印 "log: 第二个then" | 微任务队列 | 当前执行上下文 | | --- | --- | | [ ] | then2-callback

  6. 微任务队列清空,无新的宏任务,执行结束。

例② 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
下面我们来复盘一下执行过程

  1. 初始化表格 | 微任务队列 | 当前执行上下文 | | --- | --- | | [ ] | global

  2. resolve(Promise.resolve()),根据我们的规则,要消耗两个微任务:首先推入第一个微任务。 | 微任务队列 | 当前执行上下文 | | --- | --- | | [ promise1消耗的第一个微任务 ] | global

  3. 执行then0,由于promise1的状态还未fulfilled,所以将then0-callback存储在promise1的innerList中 | 微任务队列 | 当前执行上下文 | promise1-innerList | --- | --- | ---| | [ promise1消耗的第一个微任务 ] | global | [ then0-callback ]

  4. 执行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 ]

  5. 取出微任务“promise1消耗的第一个微任务”并执行,将“promise1消耗的第二个微任务”加入微任务队列。 | 微任务队列 | 当前执行上下文 | promise1-innerList | then1-promise-innerList | then2-promise-innerList | --- | --- | --- | --- | --- | | [ then1-callback, promise1消耗的第二个微任务 ] | global | [ then0-callback ] | [ then2-callback ] | [ then3-callback ]

  6. 取出微任务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 ]

  7. 取出微任务“promise1消耗的第二个微任务”并执行。执行完毕后promise1的状态终于变成fulfilled了!取出promise1-innerList中任务并加入微任务队列 | 微任务队列 | 当前执行上下文 | promise1-innerList | then2-promise-innerList | --- | --- | --- | ---| | [ then2-callback, then0-callback ] | | [ ] | [ then3-callback ]

  8. 取出微任务then2-callback,打印 2 ,then2-promise状态变成fulfilled。取出then2-promise-innerList中任务并加入微任务队列

微任务队列当前执行上下文then2-promise-innerList
[ then0-callback, then3-callback ]then2-callback[ ]
  1. 取出微任务then0-callback,打印 resolved 。 | 微任务队列 | 当前执行上下文 | | --- | --- | | [ then3-callback ] | then0-callback

  2. 取出微任务then3-callback,打印 3 。 | 微任务队列 | 当前执行上下文 | | --- | --- | | [ ] | then3-callback

  3. 微任务队列清空,无新的宏任务,执行结束。

现在看看这道题,你会分析了吗?

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