<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從一道讓我失眠的 Promise 面試題深入分析

          共 55937字,需瀏覽 112分鐘

           ·

          2021-04-07 17:27

          爆肝日更  前端面試  前端工程化

          ??  點(diǎn)擊上方卡片關(guān)注

          來(lái)自作者 ITEM 投稿,原文鏈接:https://juejin.cn/post/6945319439772434469,大家可以點(diǎn)擊閱讀原文點(diǎn)贊!

          先把罪魁禍?zhǔn)讙煸谶@里給大家群毆 ??

          Promise.resolve().then(() => {
              console.log(0);
              return Promise.resolve(4);
          }).then((res) => {
              console.log(res)
          })

          Promise.resolve().then(() => {
              console.log(1);
          }).then(() => {
              console.log(2);
          }).then(() => {
              console.log(3);
          }).then(() => {
              console.log(5);
          }).then(() =>{
              console.log(6);
          })

          // 大家先思考一下

          這道面試題是無(wú)意間在微信群里看到的,據(jù)說(shuō)是某廠(chǎng)的面試題。一般關(guān)于 Promise 的面試題無(wú)非是考察宏微任務(wù)、EventLoop 之類(lèi)的,當(dāng)我認(rèn)真去分析這道題的時(shí)候,越看越不對(duì)勁,感覺(jué)有詐!這是要考察啥?

          不管了,先在瀏覽器輸出一下看看 ??

          打印結(jié)果:0、1、2、3、4、5、6 ??

          這里 4 怎么跑到 3 后面去了,不講武德?Why......

          在我看來(lái),這道題有兩個(gè) Promise.resolve(),相當(dāng)于創(chuàng)建兩個(gè)狀態(tài)為 fulfilled 的 Promise

          緊隨他們后面的第一個(gè) then 方法會(huì)交替將其執(zhí)行函數(shù)送入微任務(wù)隊(duì)列排隊(duì)執(zhí)行,所以這里的 0 和 1,大家都可以理解,但是接下來(lái)執(zhí)行的不是 console.log(res) 而是 console.log(2)

          如果說(shuō)需要等待 return Promise.resolve(4) 執(zhí)行完并將其結(jié)果和狀態(tài)同步給外部的 Promise,那么這里只需要?jiǎng)?chuàng)建一個(gè)微任務(wù)去處理就應(yīng)該可以了,也就是 4 會(huì)在 2 后面才對(duì),為啥需要創(chuàng)建兩個(gè)微任務(wù)呢? ??

          想了很久,也找很多朋友討論這個(gè)問(wèn)題,都沒(méi)有得到有說(shuō)服力的結(jié)論,真是百思不得其解!這樣死摳細(xì)節(jié),感覺(jué)有點(diǎn)浪費(fèi)時(shí)間,畢竟這種面試題在生產(chǎn)中并不會(huì)出現(xiàn),誰(shuí)會(huì)去寫(xiě)這么奇葩的 Promise 代碼, 放棄了,不去想了。

          然而 ??,當(dāng)天晚上夜黑風(fēng)高夜深人靜的時(shí)候,腦海里面依然輪播這道面試題,真的很想知道 Promise 內(nèi)部到底是個(gè)什么邏輯,越想越睡不著~越睡不著越想~

          9150e4e5gy1fqisabwf6sg208a07pgnh.gif

          無(wú)奈之下,決定參考 Promise A+ 規(guī)范手寫(xiě)一版 Promise,看看能不能從實(shí)現(xiàn)細(xì)節(jié)中找到蛛絲馬跡。為了方便大家理解,下面我會(huì)利用不同 ?? 來(lái)介紹手寫(xiě)的細(xì)節(jié)和思路。文章最后會(huì)依據(jù)實(shí)現(xiàn)細(xì)節(jié)來(lái)探討這道面試題,有手寫(xiě)經(jīng)驗(yàn)的可以直接跳過(guò)手寫(xiě) Promise 實(shí)現(xiàn)過(guò)程,看最后的結(jié)論。

          手寫(xiě)前需要先了解這些

          如果感覺(jué)對(duì) Promise 還不太熟悉的就先移步 Promise 入門(mén)[1],稍微做一下知識(shí)預(yù)習(xí),了解一下 Promise 的常規(guī)用法。

          什么是宏任務(wù)與微任務(wù)?

          我們都知道 Js 是單線(xiàn)程都,但是一些高耗時(shí)操作就帶來(lái)了進(jìn)程阻塞問(wèn)題。為了解決這個(gè)問(wèn)題,Js 有兩種任務(wù)的執(zhí)行模式:同步模式(Synchronous)和異步模式(Asynchronous)

          在異步模式下,創(chuàng)建異步任務(wù)主要分為宏任務(wù)與微任務(wù)兩種。ES6 規(guī)范中,宏任務(wù)(Macrotask) 稱(chēng)為 Task, 微任務(wù)(Microtask) 稱(chēng)為 Jobs。宏任務(wù)是由宿主(瀏覽器、Node)發(fā)起的,而微任務(wù)由 JS 自身發(fā)起。

          宏任務(wù)與微任務(wù)的幾種創(chuàng)建方式 ??

          宏任務(wù)(Macrotask)微任務(wù)(Microtask)
          setTimeoutrequestAnimationFrame(有爭(zhēng)議)
          setIntervalMutationObserver(瀏覽器環(huán)境)
          MessageChannelPromise.[ then/catch/finally ]
          I/O,事件隊(duì)列process.nextTick(Node 環(huán)境)
          setImmediate(Node 環(huán)境)queueMicrotask
          script(整體代碼塊)

          如何理解 script(整體代碼塊)是個(gè)宏任務(wù)呢 ??

          實(shí)際上如果同時(shí)存在兩個(gè) script 代碼塊,會(huì)首先在執(zhí)行第一個(gè) script 代碼塊中的同步代碼,如果這個(gè)過(guò)程中創(chuàng)建了微任務(wù)并進(jìn)入了微任務(wù)隊(duì)列,第一個(gè) script 同步代碼執(zhí)行完之后,會(huì)首先去清空微任務(wù)隊(duì)列,再去開(kāi)啟第二個(gè) script 代碼塊的執(zhí)行。所以這里應(yīng)該就可以理解 script(整體代碼塊)為什么會(huì)是宏任務(wù)。

          什么是 EventLoop ?

          先來(lái)看個(gè)圖

          EventLoop.png
          1. 判斷宏任務(wù)隊(duì)列是否為空

            • 不空 --> 執(zhí)行最早進(jìn)入隊(duì)列的任務(wù) --> 執(zhí)行下一步
            • 空 --> 執(zhí)行下一步
          2. 判斷微任務(wù)隊(duì)列是否為空

            • 不空 --> 執(zhí)行最早進(jìn)入隊(duì)列的任務(wù) --> 繼續(xù)檢查微任務(wù)隊(duì)列空不空
            • 空 --> 執(zhí)行下一步

          因?yàn)槭状螆?zhí)行宏隊(duì)列中會(huì)有 script(整體代碼塊)任務(wù),所以實(shí)際上就是 Js 解析完成后,在異步任務(wù)中,會(huì)先執(zhí)行完所有的微任務(wù),這里也是很多面試題喜歡考察的。需要注意的是,新創(chuàng)建的微任務(wù)會(huì)立即進(jìn)入微任務(wù)隊(duì)列排隊(duì)執(zhí)行,不需要等待下一次輪回。

          什么是 Promise A+ 規(guī)范?

          看到 A+ 肯定會(huì)想到是不是還有 A,事實(shí)上確實(shí)有。其實(shí) Promise 有多種規(guī)范,除了前面的 Promise A、promise A+ 還有 Promise/B,Promise/D。目前我們使用的 Promise 是基于 Promise A+ 規(guī)范實(shí)現(xiàn)的,感興趣的移步 Promise A+規(guī)范[2]了解一下,這里不贅述。

          檢驗(yàn)一份手寫(xiě) Promise 靠不靠譜,通過(guò) Promise A+ 規(guī)范自然是基本要求,這里我們可以借助 promises-aplus-tests[3] 來(lái)檢測(cè)我們的代碼是否符合規(guī)范,后面我會(huì)講到如何使用它。

          手寫(xiě)開(kāi)始

          很多手寫(xiě)版本都是使用 setTimeout 去做異步處理,但是 setTimeout 屬于宏任務(wù),這與 Promise 是個(gè)微任務(wù)相矛盾,所以我打算選擇一種創(chuàng)建微任務(wù)的方式去實(shí)現(xiàn)我們的手寫(xiě)代碼。

          這里我們有幾種選擇,一種就是 Promise A+ 規(guī)范中也提到的,process.nextTick( Node 端 ) 與 MutationObserver( 瀏覽器端 ),考慮到利用這兩種方式需要做環(huán)境判斷,所以在這里我們就推薦另外一種創(chuàng)建微任務(wù)的方式 queueMicrotask,了解更多 --> 在 JavaScript 中通過(guò) queueMicrotask() 使用微任務(wù)[4];

          一、Promise 核心邏輯實(shí)現(xiàn)

          我們先簡(jiǎn)單實(shí)現(xiàn)一下 Promise 的基礎(chǔ)功能。先看原生 Promise 實(shí)現(xiàn)的 ??,第一步我們要完成相同的功能。

          原生 ?? ??

          const promise = new Promise((resolve, reject) => {
             resolve('success')
             reject('err')
          })

          promise.then(value => {
            console.log('resolve', value)
          }, reason => {
            console.log('reject', reason)
          })

          // 輸出 resolve success

          我們來(lái)分析一下基本原理

          1. Promise 是一個(gè)類(lèi),在執(zhí)行這個(gè)類(lèi)的時(shí)候會(huì)傳入一個(gè)執(zhí)行器,這個(gè)執(zhí)行器會(huì)立即執(zhí)行
          2. Promise 會(huì)有三種狀態(tài)
            • Pending 等待
            • Fulfilled 完成
            • Rejected 失敗
          3. 狀態(tài)只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但發(fā)生改變便不可二次修改;
          4. Promise 中使用 resolve 和 reject 兩個(gè)函數(shù)來(lái)更改狀態(tài);
          5. then 方法內(nèi)部做但事情就是狀態(tài)判斷
            • 如果狀態(tài)是成功,調(diào)用成功回調(diào)函數(shù)
            • 如果狀態(tài)是失敗,調(diào)用失敗回調(diào)函數(shù)

          下面開(kāi)始實(shí)現(xiàn)

          1. 新建 MyPromise 類(lèi),傳入執(zhí)行器 executor

          // 新建 MyPromise.js

          // 新建 MyPromise 類(lèi)
          class MyPromise {
            constructor(executor){
              // executor 是一個(gè)執(zhí)行器,進(jìn)入會(huì)立即執(zhí)行
              executor()
            }
          }

          2. executor 傳入 resolve 和 reject 方法

          // MyPromise.js

          // 新建 MyPromise 類(lèi)
          class MyPromise {
            constructor(executor){
              // executor 是一個(gè)執(zhí)行器,進(jìn)入會(huì)立即執(zhí)行
              // 并傳入resolve和reject方法
              executor(this.resolve, this.reject)
            }
            // resolve和reject為什么要用箭頭函數(shù)?
            // 如果直接調(diào)用的話(huà),普通函數(shù)this指向的是window或者undefined
            // 用箭頭函數(shù)就可以讓this指向當(dāng)前實(shí)例對(duì)象
            // 更改成功后的狀態(tài)
            resolve = () => {}
            // 更改失敗后的狀態(tài)
            reject = () => {}
          }

          3. 狀態(tài)與結(jié)果的管理

          // MyPromise.js

          // 先定義三個(gè)常量表示狀態(tài)
          const PENDING = 'pending';
          const FULFILLED = 'fulfilled';
          const REJECTED = 'rejected';

          // 新建 MyPromise 類(lèi)
          class MyPromise {
            constructor(executor){
              // executor 是一個(gè)執(zhí)行器,進(jìn)入會(huì)立即執(zhí)行
              // 并傳入resolve和reject方法
              executor(this.resolve, this.reject)
            }

            // 儲(chǔ)存狀態(tài)的變量,初始值是 pending
            status = PENDING;

            // resolve和reject為什么要用箭頭函數(shù)?
            // 如果直接調(diào)用的話(huà),普通函數(shù)this指向的是window或者undefined
            // 用箭頭函數(shù)就可以讓this指向當(dāng)前實(shí)例對(duì)象
            // 成功之后的值
            value = null;
            // 失敗之后的原因
            reason = null;

            // 更改成功后的狀態(tài)
            resolve = (value) => {
              // 只有狀態(tài)是等待,才執(zhí)行狀態(tài)修改
              if (this.status === PENDING) {
                // 狀態(tài)修改為成功
                this.status = FULFILLED;
                // 保存成功之后的值
                this.value = value;
              }
            }

            // 更改失敗后的狀態(tài)
            reject = (reason) => {
              // 只有狀態(tài)是等待,才執(zhí)行狀態(tài)修改
              if (this.status === PENDING) {
                // 狀態(tài)成功為失敗
                this.status = REJECTED;
                // 保存失敗后的原因
                this.reason = reason;
              }
            }
          }

          4. then 的簡(jiǎn)單實(shí)現(xiàn)

          // MyPromise.js

          then(onFulfilled, onRejected) {
            // 判斷狀態(tài)
            if (this.status === FULFILLED) {
              // 調(diào)用成功回調(diào),并且把值返回
              onFulfilled(this.value);
            } else if (this.status === REJECTED) {
              // 調(diào)用失敗回調(diào),并且把原因返回
              onRejected(this.reason);
            }
          }

          5. 使用 module.exports 對(duì)外暴露 MyPromise 類(lèi)

          // MyPromise.js
          module.exports = MyPromise;

          看一下我們目前實(shí)現(xiàn)的完整代碼??

          // MyPromise.js

          // 先定義三個(gè)常量表示狀態(tài)
          const PENDING = 'pending';
          const FULFILLED = 'fulfilled';
          const REJECTED = 'rejected';

          // 新建 MyPromise 類(lèi)
          class MyPromise {
            constructor(executor){
              // executor 是一個(gè)執(zhí)行器,進(jìn)入會(huì)立即執(zhí)行
              // 并傳入resolve和reject方法
              executor(this.resolve, this.reject)
            }

            // 儲(chǔ)存狀態(tài)的變量,初始值是 pending
            status = PENDING;

            // resolve和reject為什么要用箭頭函數(shù)?
            // 如果直接調(diào)用的話(huà),普通函數(shù)this指向的是window或者undefined
            // 用箭頭函數(shù)就可以讓this指向當(dāng)前實(shí)例對(duì)象
            // 成功之后的值
            value = null;
            // 失敗之后的原因
            reason = null;

            // 更改成功后的狀態(tài)
            resolve = (value) => {
              // 只有狀態(tài)是等待,才執(zhí)行狀態(tài)修改
              if (this.status === PENDING) {
                // 狀態(tài)修改為成功
                this.status = FULFILLED;
                // 保存成功之后的值
                this.value = value;
              }
            }

            // 更改失敗后的狀態(tài)
            reject = (reason) => {
              // 只有狀態(tài)是等待,才執(zhí)行狀態(tài)修改
              if (this.status === PENDING) {
                // 狀態(tài)成功為失敗
                this.status = REJECTED;
                // 保存失敗后的原因
                this.reason = reason;
              }
            }

            then(onFulfilled, onRejected) {
              // 判斷狀態(tài)
              if (this.status === FULFILLED) {
                // 調(diào)用成功回調(diào),并且把值返回
                onFulfilled(this.value);
              } else if (this.status === REJECTED) {
                // 調(diào)用失敗回調(diào),并且把原因返回
                onRejected(this.reason);
              }
            }
          }

          module.exports = MyPromise

          使用我的手寫(xiě)代碼執(zhí)行一下上面那個(gè) ??

          // 新建 test.js

          // 引入我們的 MyPromise.js
          const MyPromise = require('./MyPromise')
          const promise = new MyPromise((resolve, reject) => {
             resolve('success')
             reject('err')
          })

          promise.then(value => {
            console.log('resolve', value)
          }, reason => {
            console.log('reject', reason)
          })

          // 執(zhí)行結(jié)果:resolve success

          執(zhí)行結(jié)果符合我們的預(yù)期,第一步完成了 ??????

          二、在 Promise 類(lèi)中加入異步邏輯

          上面還沒(méi)有經(jīng)過(guò)異步處理,如果有異步邏輯加如來(lái)會(huì)帶來(lái)一些問(wèn)題,例如:

          // test.js

          const MyPromise = require('./MyPromise')
          const promise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
              resolve('success')
            }, 2000);
          })

          promise.then(value => {
            console.log('resolve', value)
          }, reason => {
            console.log('reject', reason)
          })

          // 沒(méi)有打印信息!!!

          分析原因

          主線(xiàn)程代碼立即執(zhí)行,setTimeout 是異步代碼,then 會(huì)馬上執(zhí)行,這個(gè)時(shí)候判斷 Promise 狀態(tài),狀態(tài)是 Pending,然而之前并沒(méi)有判斷等待這個(gè)狀態(tài)

          這里就需要我們處理一下 Pending 狀態(tài),我們改造一下之前的代碼 ??

          1. 緩存成功與失敗回調(diào)

          // MyPromise.js

          // MyPromise 類(lèi)中新增
          // 存儲(chǔ)成功回調(diào)函數(shù)
          onFulfilledCallback = null;
          // 存儲(chǔ)失敗回調(diào)函數(shù)
          onRejectedCallback = null;

          2. then 方法中的 Pending 的處理

          // MyPromise.js

          then(onFulfilled, onRejected) {
            // 判斷狀態(tài)
            if (this.status === FULFILLED) {
              // 調(diào)用成功回調(diào),并且把值返回
              onFulfilled(this.value);
            } else if (this.status === REJECTED) {
              // 調(diào)用失敗回調(diào),并且把原因返回
              onRejected(this.reason);
            } else if (this.status === PENDING) {
              // ==== 新增 ====
              // 因?yàn)椴恢篮竺鏍顟B(tài)的變化情況,所以將成功回調(diào)和失敗回調(diào)存儲(chǔ)起來(lái)
              // 等到執(zhí)行成功失敗函數(shù)的時(shí)候再傳遞
              this.onFulfilledCallback = onFulfilled;
              this.onRejectedCallback = onRejected;
            }
          }

          3. resolve 與 reject 中調(diào)用回調(diào)函數(shù)

          // MyPromise.js

          // 更改成功后的狀態(tài)
          resolve = (value) => {
            // 只有狀態(tài)是等待,才執(zhí)行狀態(tài)修改
            if (this.status === PENDING) {
              // 狀態(tài)修改為成功
              this.status = FULFILLED;
              // 保存成功之后的值
              this.value = value;
              // ==== 新增 ====
              // 判斷成功回調(diào)是否存在,如果存在就調(diào)用
              this.onFulfilledCallback && this.onFulfilledCallback(value);
            }
          }
          // MyPromise.js
          // 更改失敗后的狀態(tài)
          reject = (reason) => {
            // 只有狀態(tài)是等待,才執(zhí)行狀態(tài)修改
            if (this.status === PENDING) {
              // 狀態(tài)成功為失敗
              this.status = REJECTED;
              // 保存失敗后的原因
              this.reason = reason;
              // ==== 新增 ====
              // 判斷失敗回調(diào)是否存在,如果存在就調(diào)用
              this.onRejectedCallback && this.onRejectedCallback(reason)
            }
          }

          我們?cè)賵?zhí)行一下上面的 ??

          // test.js

          const MyPromise = require('./MyPromise')
          const promise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
              resolve('success')
            }, 2000);
          })

          promise.then(value => {
            console.log('resolve', value)
          }, reason => {
            console.log('reject', reason)
          })

          // 等待 2s 輸出 resolve success

          目前已經(jīng)可以簡(jiǎn)單處理異步問(wèn)題了 ??

          三、實(shí)現(xiàn) then 方法多次調(diào)用添加多個(gè)處理函數(shù)

          Promise 的 then 方法是可以被多次調(diào)用的。這里如果有三個(gè) then 的調(diào)用,如果是同步回調(diào),那么直接返回當(dāng)前的值就行;如果是異步回調(diào),那么保存的成功失敗的回調(diào),需要用不同的值保存,因?yàn)槎蓟ゲ幌嗤V暗拇a需要改進(jìn)。

          同樣的先看一個(gè) ??

          // test.js

          const MyPromise = require('./MyPromise')
          const promise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
              resolve('success')
            }, 2000);
          })

          promise.then(value => {
            console.log(1)
            console.log('resolve', value)
          })

          promise.then(value => {
            console.log(2)
            console.log('resolve', value)
          })

          promise.then(value => {
            console.log(3)
            console.log('resolve', value)
          })

          // 3
          // resolve success

          目前的代碼只能輸出:3 resolve success,怎么可以把 1、2 弄丟呢!

          我們應(yīng)該一視同仁,保證所有 then 中的回調(diào)函數(shù)都可以執(zhí)行 ?? 繼續(xù)改造

          1. MyPromise 類(lèi)中新增兩個(gè)數(shù)組

          // MyPromise.js

          // 存儲(chǔ)成功回調(diào)函數(shù)
          // onFulfilledCallback = null;
          onFulfilledCallbacks = [];
          // 存儲(chǔ)失敗回調(diào)函數(shù)
          // onRejectedCallback = null;
          onRejectedCallbacks = [];

          2. 回調(diào)函數(shù)存入數(shù)組中

          // MyPromise.js

          then(onFulfilled, onRejected) {
            // 判斷狀態(tài)
            if (this.status === FULFILLED) {
              // 調(diào)用成功回調(diào),并且把值返回
              onFulfilled(this.value);
            } else if (this.status === REJECTED) {
              // 調(diào)用失敗回調(diào),并且把原因返回
              onRejected(this.reason);
            } else if (this.status === PENDING) {
              // ==== 新增 ====
              // 因?yàn)椴恢篮竺鏍顟B(tài)的變化,這里先將成功回調(diào)和失敗回調(diào)存儲(chǔ)起來(lái)
              // 等待后續(xù)調(diào)用
              this.onFulfilledCallbacks.push(onFulfilled);
              this.onRejectedCallbacks.push(onRejected);
            }
          }

          3. 循環(huán)調(diào)用成功和失敗回調(diào)

          // MyPromise.js

          // 更改成功后的狀態(tài)
          resolve = (value) => {
            // 只有狀態(tài)是等待,才執(zhí)行狀態(tài)修改
            if (this.status === PENDING) {
              // 狀態(tài)修改為成功
              this.status = FULFILLED;
              // 保存成功之后的值
              this.value = value;
              // ==== 新增 ====
              // resolve里面將所有成功的回調(diào)拿出來(lái)執(zhí)行
              while (this.onFulfilledCallbacks.length) {
                // Array.shift() 取出數(shù)組第一個(gè)元素,然后()調(diào)用,shift不是純函數(shù),取出后,數(shù)組將失去該元素,直到數(shù)組為空
                this.onFulfilledCallbacks.shift()(value)
              }
            }
          }
          // MyPromise.js

          // 更改失敗后的狀態(tài)
          reject = (reason) => {
            // 只有狀態(tài)是等待,才執(zhí)行狀態(tài)修改
            if (this.status === PENDING) {
              // 狀態(tài)成功為失敗
              this.status = REJECTED;
              // 保存失敗后的原因
              this.reason = reason;
              // ==== 新增 ====
              // resolve里面將所有失敗的回調(diào)拿出來(lái)執(zhí)行
              while (this.onRejectedCallbacks.length) {
                this.onRejectedCallbacks.shift()(reason)
              }
            }
          }

          再來(lái)運(yùn)行一下,看看結(jié)果 ??

          1
          resolve success
          2
          resolve success
          3
          resolve success

          ?????? 完美,繼續(xù)

          四、實(shí)現(xiàn) then 方法的鏈?zhǔn)秸{(diào)用

          then 方法要鏈?zhǔn)秸{(diào)用那么就需要返回一個(gè) Promise 對(duì)象
          then 方法里面 return 一個(gè)返回值作為下一個(gè) then 方法的參數(shù),如果是 return 一個(gè) Promise 對(duì)象,那么就需要判斷它的狀態(tài)

          舉個(gè)栗子 ??

          // test.js

          const MyPromise = require('./MyPromise')
          const promise = new MyPromise((resolve, reject) => {
            // 目前這里只處理同步的問(wèn)題
            resolve('success')
          })

          function other ({
            return new MyPromise((resolve, reject) =>{
              resolve('other')
            })
          }
          promise.then(value => {
            console.log(1)
            console.log('resolve', value)
            return other()
          }).then(value => {
            console.log(2)
            console.log('resolve', value)
          })

          用目前的手寫(xiě)代碼運(yùn)行的時(shí)候會(huì)報(bào)錯(cuò) ?? 無(wú)法鏈?zhǔn)秸{(diào)用

          }).then(value => {
            ^

          TypeError: Cannot read property 'then' of undefined

          接著改 ??

          // MyPromise.js

          class MyPromise {
            ......
            then(onFulfilled, onRejected) {
              // ==== 新增 ====
              // 為了鏈?zhǔn)秸{(diào)用這里直接創(chuàng)建一個(gè) MyPromise,并在后面 return 出去
              const promise2 = new MyPromise((resolve, reject) => {
                // 這里的內(nèi)容在執(zhí)行器中,會(huì)立即執(zhí)行
                if (this.status === FULFILLED) {
                  // 獲取成功回調(diào)函數(shù)的執(zhí)行結(jié)果
                  const x = onFulfilled(this.value);
                  // 傳入 resolvePromise 集中處理
                  resolvePromise(x, resolve, reject);
                } else if (this.status === REJECTED) {
                  onRejected(this.reason);
                } else if (this.status === PENDING) {
                  this.onFulfilledCallbacks.push(onFulfilled);
                  this.onRejectedCallbacks.push(onRejected);
                }
              })

              return promise2;
            }
          }

          function resolvePromise(x, resolve, reject{
            // 判斷x是不是 MyPromise 實(shí)例對(duì)象
            if(x instanceof MyPromise) {
              // 執(zhí)行 x,調(diào)用 then 方法,目的是將其狀態(tài)變?yōu)?nbsp;fulfilled 或者 rejected
              // x.then(value => resolve(value), reason => reject(reason))
              // 簡(jiǎn)化之后
              x.then(resolve, reject)
            } else{
              // 普通值
              resolve(x)
            }
          }

          執(zhí)行一下,結(jié)果 ??

          1
          resolve success
          2
          resolve other

          em... 符合預(yù)期 ??

          五、then 方法鏈?zhǔn)秸{(diào)用識(shí)別 Promise 是否返回自己

          如果 then 方法返回的是自己的 Promise 對(duì)象,則會(huì)發(fā)生循環(huán)調(diào)用,這個(gè)時(shí)候程序會(huì)報(bào)錯(cuò)

          例如下面這種情況 ??

          // test.js

          const promise = new Promise((resolve, reject) => {
            resolve(100)
          })
          const p1 = promise.then(value => {
            console.log(value)
            return p1
          })

          使用原生 Promise 執(zhí)行這個(gè)代碼,會(huì)報(bào)類(lèi)型錯(cuò)誤

          100
          Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

          我們?cè)?MyPromise 實(shí)現(xiàn)一下

          // MyPromise.js

          class MyPromise {
            ......
            then(onFulfilled, onRejected) {
              const promise2 = new MyPromise((resolve, reject) => {
                if (this.status === FULFILLED) {
                  const x = onFulfilled(this.value);
                  // resolvePromise 集中處理,將 promise2 傳入
                  resolvePromise(promise2, x, resolve, reject);
                } else if (this.status === REJECTED) {
                  onRejected(this.reason);
                } else if (this.status === PENDING) {
                  this.onFulfilledCallbacks.push(onFulfilled);
                  this.onRejectedCallbacks.push(onRejected);
                }
              })

              return promise2;
            }
          }

          function resolvePromise(promise2, x, resolve, reject{
            // 如果相等了,說(shuō)明return的是自己,拋出類(lèi)型錯(cuò)誤并返回
            if (promise2 === x) {
              return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
            }
            if(x instanceof MyPromise) {
              x.then(resolve, reject)
            } else{
              resolve(x)
            }
          }

          執(zhí)行一下,竟然報(bào)錯(cuò)了 ??

                  resolvePromise(promise2, x, resolve, reject);
                                 ^

          ReferenceError: Cannot access 'promise2' before initialization

          為啥會(huì)報(bào)錯(cuò)呢?從錯(cuò)誤提示可以看出,我們必須要等 promise2 完成初始化。這個(gè)時(shí)候我們就要用上宏微任務(wù)和事件循環(huán)的知識(shí)了,這里就需要?jiǎng)?chuàng)建一個(gè)異步函數(shù)去等待 promise2 完成初始化,前面我們已經(jīng)確認(rèn)了創(chuàng)建微任務(wù)的技術(shù)方案 --> queueMicrotask

          // MyPromise.js

          class MyPromise {
            ......
            then(onFulfilled, onRejected) {
              const promise2 = new MyPromise((resolve, reject) => {
                if (this.status === FULFILLED) {
                  // ==== 新增 ====
                  // 創(chuàng)建一個(gè)微任務(wù)等待 promise2 完成初始化
                  queueMicrotask(() => {
                    // 獲取成功回調(diào)函數(shù)的執(zhí)行結(jié)果
                    const x = onFulfilled(this.value);
                    // 傳入 resolvePromise 集中處理
                    resolvePromise(promise2, x, resolve, reject);
                  })
                } else if (this.status === REJECTED) {
                ......
              })

              return promise2;
            }
          }

          執(zhí)行一下

          // test.js

          const MyPromise = require('./MyPromise')
          const promise = new MyPromise((resolve, reject) => {
              resolve('success')
          })

          // 這個(gè)時(shí)候?qū)romise定義一個(gè)p1,然后返回的時(shí)候返回p1這個(gè)promise
          const p1 = promise.then(value => {
             console.log(1)
             console.log('resolve', value)
             return p1
          })

          // 運(yùn)行的時(shí)候會(huì)走reject
          p1.then(value => {
            console.log(2)
            console.log('resolve', value)
          }, reason => {
            console.log(3)
            console.log(reason.message)
          })

          這里得到我們的結(jié)果 ??

          1
          resolve success
          3
          Chaining cycle detected for promise #<Promise>

          哈哈,搞定 ?? 開(kāi)始下一步

          六、捕獲錯(cuò)誤及 then 鏈?zhǔn)秸{(diào)用其他狀態(tài)代碼補(bǔ)充

          目前還缺少重要的一個(gè)環(huán)節(jié),就是我們的錯(cuò)誤捕獲還沒(méi)有處理

          1. 捕獲執(zhí)行器錯(cuò)誤

          捕獲執(zhí)行器中的代碼,如果執(zhí)行器中有代碼錯(cuò)誤,那么 Promise 的狀態(tài)要變?yōu)槭?/p>

          // MyPromise.js

          constructor(executor){
            // ==== 新增 ====
            // executor 是一個(gè)執(zhí)行器,進(jìn)入會(huì)立即執(zhí)行
            // 并傳入resolve和reject方法
            try {
              executor(this.resolve, this.reject)
            } catch (error) {
              // 如果有錯(cuò)誤,就直接執(zhí)行 reject
              this.reject(error)
            }
          }

          驗(yàn)證一下:

          // test.js

          const MyPromise = require('./MyPromise')
          const promise = new MyPromise((resolve, reject) => {
              // resolve('success')
              throw new Error('執(zhí)行器錯(cuò)誤')
          })

          promise.then(value => {
            console.log(1)
            console.log('resolve', value)
          }, reason => {
            console.log(2)
            console.log(reason.message)
          })

          執(zhí)行結(jié)果 ??

          2
          執(zhí)行器錯(cuò)誤

          OK,通過(guò) ??

          2. then 執(zhí)行的時(shí)錯(cuò)誤捕獲

          // MyPromise.js

          then(onFulfilled, onRejected) {
            // 為了鏈?zhǔn)秸{(diào)用這里直接創(chuàng)建一個(gè) MyPromise,并在后面 return 出去
            const promise2 = new MyPromise((resolve, reject) => {
              // 判斷狀態(tài)
              if (this.status === FULFILLED) {
                // 創(chuàng)建一個(gè)微任務(wù)等待 promise2 完成初始化
                queueMicrotask(() => {
                  // ==== 新增 ====
                  try {
                    // 獲取成功回調(diào)函數(shù)的執(zhí)行結(jié)果
                    const x = onFulfilled(this.value);
                    // 傳入 resolvePromise 集中處理
                    resolvePromise(promise2, x, resolve, reject);
                  } catch (error) {
                    reject(error)
                  }
                })
              } else if (this.status === REJECTED) {
                // 調(diào)用失敗回調(diào),并且把原因返回
                onRejected(this.reason);
              } else if (this.status === PENDING) {
                // 等待
                // 因?yàn)椴恢篮竺鏍顟B(tài)的變化情況,所以將成功回調(diào)和失敗回調(diào)存儲(chǔ)起來(lái)
                // 等到執(zhí)行成功失敗函數(shù)的時(shí)候再傳遞
                this.onFulfilledCallbacks.push(onFulfilled);
                this.onRejectedCallbacks.push(onRejected);
              }
            })

            return promise2;
          }

          驗(yàn)證一下:

          // test.js

          const MyPromise = require('./MyPromise')
          const promise = new MyPromise((resolve, reject) => {
              resolve('success')
              // throw new Error('執(zhí)行器錯(cuò)誤')
           })

          // 第一個(gè)then方法中的錯(cuò)誤要在第二個(gè)then方法中捕獲到
          promise.then(value => {
            console.log(1)
            console.log('resolve', value)
            throw new Error('then error')
          }, reason => {
            console.log(2)
            console.log(reason.message)
          }).then(value => {
            console.log(3)
            console.log(value);
          }, reason => {
            console.log(4)
            console.log(reason.message)
          })

          執(zhí)行結(jié)果 ??

          1
          resolve success
          4
          then error

          這里成功打印了 1 中拋出的錯(cuò)誤 then error

          七、參考 fulfilled 狀態(tài)下的處理方式,對(duì) rejected 和 pending 狀態(tài)進(jìn)行改造

          改造內(nèi)容包括:

          1. 增加異步狀態(tài)下的鏈?zhǔn)秸{(diào)用
          2. 增加回調(diào)函數(shù)執(zhí)行結(jié)果的判斷
          3. 增加識(shí)別 Promise 是否返回自己
          4. 增加錯(cuò)誤捕獲
          // MyPromise.js

          then(onFulfilled, onRejected) {
            // 為了鏈?zhǔn)秸{(diào)用這里直接創(chuàng)建一個(gè) MyPromise,并在后面 return 出去
            const promise2 = new MyPromise((resolve, reject) => {
              // 判斷狀態(tài)
              if (this.status === FULFILLED) {
                // 創(chuàng)建一個(gè)微任務(wù)等待 promise2 完成初始化
                queueMicrotask(() => {
                  try {
                    // 獲取成功回調(diào)函數(shù)的執(zhí)行結(jié)果
                    const x = onFulfilled(this.value);
                    // 傳入 resolvePromise 集中處理
                    resolvePromise(promise2, x, resolve, reject);
                  } catch (error) {
                    reject(error)
                  }
                })
              } else if (this.status === REJECTED) {
                // ==== 新增 ====
                // 創(chuàng)建一個(gè)微任務(wù)等待 promise2 完成初始化
                queueMicrotask(() => {
                  try {
                    // 調(diào)用失敗回調(diào),并且把原因返回
                    const x = onRejected(this.reason);
                    // 傳入 resolvePromise 集中處理
                    resolvePromise(promise2, x, resolve, reject);
                  } catch (error) {
                    reject(error)
                  }
                })
              } else if (this.status === PENDING) {
                // 等待
                // 因?yàn)椴恢篮竺鏍顟B(tài)的變化情況,所以將成功回調(diào)和失敗回調(diào)存儲(chǔ)起來(lái)
                // 等到執(zhí)行成功失敗函數(shù)的時(shí)候再傳遞
                this.onFulfilledCallbacks.push(() => {
                  // ==== 新增 ====
                  queueMicrotask(() => {
                    try {
                      // 獲取成功回調(diào)函數(shù)的執(zhí)行結(jié)果
                      const x = onFulfilled(this.value);
                      // 傳入 resolvePromise 集中處理
                      resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                      reject(error)
                    }
                  })
                });
                this.onRejectedCallbacks.push(() => {
                  // ==== 新增 ====
                  queueMicrotask(() => {
                    try {
                      // 調(diào)用失敗回調(diào),并且把原因返回
                      const x = onRejected(this.reason);
                      // 傳入 resolvePromise 集中處理
                      resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                      reject(error)
                    }
                  })
                });
              }
            })

            return promise2;
          }

          八、then 中的參數(shù)變?yōu)榭蛇x

          上面我們處理 then 方法的時(shí)候都是默認(rèn)傳入 onFulfilled、onRejected 兩個(gè)回調(diào)函數(shù),但是實(shí)際上原生 Promise 是可以選擇參數(shù)的單傳或者不傳,都不會(huì)影響執(zhí)行。

          例如下面這種 ??

          // test.js

          const promise = new Promise((resolve, reject) => {
            resolve(100)
          })

          promise
            .then()
            .then()
            .then()
            .then(value => console.log(value))

          // 輸出 100

          所以我們需要對(duì) then 方法做一點(diǎn)小小的調(diào)整

          // MyPromise.js

          then(onFulfilled, onRejected) {
            // 如果不傳,就使用默認(rèn)函數(shù)
            onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
            onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};

            // 為了鏈?zhǔn)秸{(diào)用這里直接創(chuàng)建一個(gè) MyPromise,并在后面 return 出去
            const promise2 = new MyPromise((resolve, reject) => {
            ......
          }

          改造完自然是需要驗(yàn)證一下的

          先看情況一:resolve 之后

          // test.js

          const MyPromise = require('./MyPromise')
          const promise = new MyPromise((resolve, reject) => {
            resolve('succ')
          })

          promise.then().then().then(value => console.log(value))

          // 打印 succ

          先看情況一:reject 之后

          // test.js

          const MyPromise = require('./MyPromise')
          const promise = new MyPromise((resolve, reject) => {
            reject('err')
          })

          promise.then().then().then(value => console.log(value), reason => console.log(reason))

          // 打印 err

          寫(xiě)到這里,麻雀版的 Promise 基本完成了,鼓掌 ??????

          九、實(shí)現(xiàn) resolve 與 reject 的靜態(tài)調(diào)用

          就像開(kāi)頭掛的那道面試題使用 return Promise.resolve 來(lái)返回一個(gè) Promise 對(duì)象,我們用現(xiàn)在的手寫(xiě)代碼嘗試一下

          const MyPromise = require('./MyPromise')

          MyPromise.resolve().then(() => {
              console.log(0);
              return MyPromise.resolve(4);
          }).then((res) => {
              console.log(res)
          })

          結(jié)果它報(bào)錯(cuò)了 ??

          MyPromise.resolve().then(() => {
                    ^

          TypeError: MyPromise.resolve is not a function

          除了 Promise.resolve 還有 Promise.reject 的用法,我們都要去支持,接下來(lái)我們來(lái)實(shí)現(xiàn)一下

          // MyPromise.js

          MyPromise {
            ......
            // resolve 靜態(tài)方法
            static resolve (parameter) {
              // 如果傳入 MyPromise 就直接返回
              if (parameter instanceof MyPromise) {
                return parameter;
              }

              // 轉(zhuǎn)成常規(guī)方式
              return new MyPromise(resolve =>  {
                resolve(parameter);
              });
            }

            // reject 靜態(tài)方法
            static reject (reason) {
              return new MyPromise((resolve, reject) => {
                reject(reason);
              });
            }
          }

          這樣我們?cè)贉y(cè)試上面的 ?? 就不會(huì)有問(wèn)題啦

          執(zhí)行結(jié)果 ??

          0
          4

          到這里手寫(xiě)工作就基本完成了,前面主要為了方便理解,所以有一些冗余代碼,我規(guī)整一下

          // MyPromise.js

          // 先定義三個(gè)常量表示狀態(tài)
          const PENDING = 'pending';
          const FULFILLED = 'fulfilled';
          const REJECTED = 'rejected';

          // 新建 MyPromise 類(lèi)
          class MyPromise {
            constructor(executor){
              // executor 是一個(gè)執(zhí)行器,進(jìn)入會(huì)立即執(zhí)行
              // 并傳入resolve和reject方法
              try {
                executor(this.resolve, this.reject)
              } catch (error) {
                this.reject(error)
              }
            }

            // 儲(chǔ)存狀態(tài)的變量,初始值是 pending
            status = PENDING;
            // 成功之后的值
            value = null;
            // 失敗之后的原因
            reason = null;

            // 存儲(chǔ)成功回調(diào)函數(shù)
            onFulfilledCallbacks = [];
            // 存儲(chǔ)失敗回調(diào)函數(shù)
            onRejectedCallbacks = [];

            // 更改成功后的狀態(tài)
            resolve = (value) => {
              // 只有狀態(tài)是等待,才執(zhí)行狀態(tài)修改
              if (this.status === PENDING) {
                // 狀態(tài)修改為成功
                this.status = FULFILLED;
                // 保存成功之后的值
                this.value = value;
                // resolve里面將所有成功的回調(diào)拿出來(lái)執(zhí)行
                while (this.onFulfilledCallbacks.length) {
                  // Array.shift() 取出數(shù)組第一個(gè)元素,然后()調(diào)用,shift不是純函數(shù),取出后,數(shù)組將失去該元素,直到數(shù)組為空
                  this.onFulfilledCallbacks.shift()(value)
                }
              }
            }

            // 更改失敗后的狀態(tài)
            reject = (reason) => {
              // 只有狀態(tài)是等待,才執(zhí)行狀態(tài)修改
              if (this.status === PENDING) {
                // 狀態(tài)成功為失敗
                this.status = REJECTED;
                // 保存失敗后的原因
                this.reason = reason;
                // resolve里面將所有失敗的回調(diào)拿出來(lái)執(zhí)行
                while (this.onRejectedCallbacks.length) {
                  this.onRejectedCallbacks.shift()(reason)
                }
              }
            }

            then(onFulfilled, onRejected) {
              const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
              const realOnRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};

              // 為了鏈?zhǔn)秸{(diào)用這里直接創(chuàng)建一個(gè) MyPromise,并在后面 return 出去
              const promise2 = new MyPromise((resolve, reject) => {
                const fulfilledMicrotask = () =>  {
                  // 創(chuàng)建一個(gè)微任務(wù)等待 promise2 完成初始化
                  queueMicrotask(() => {
                    try {
                      // 獲取成功回調(diào)函數(shù)的執(zhí)行結(jié)果
                      const x = realOnFulfilled(this.value);
                      // 傳入 resolvePromise 集中處理
                      resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                      reject(error)
                    }
                  })
                }

                const rejectedMicrotask = () => {
                  // 創(chuàng)建一個(gè)微任務(wù)等待 promise2 完成初始化
                  queueMicrotask(() => {
                    try {
                      // 調(diào)用失敗回調(diào),并且把原因返回
                      const x = realOnRejected(this.reason);
                      // 傳入 resolvePromise 集中處理
                      resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                      reject(error)
                    }
                  })
                }
                // 判斷狀態(tài)
                if (this.status === FULFILLED) {
                  fulfilledMicrotask()
                } else if (this.status === REJECTED) {
                  rejectedMicrotask()
                } else if (this.status === PENDING) {
                  // 等待
                  // 因?yàn)椴恢篮竺鏍顟B(tài)的變化情況,所以將成功回調(diào)和失敗回調(diào)存儲(chǔ)起來(lái)
                  // 等到執(zhí)行成功失敗函數(shù)的時(shí)候再傳遞
                  this.onFulfilledCallbacks.push(fulfilledMicrotask);
                  this.onRejectedCallbacks.push(rejectedMicrotask);
                }
              })

              return promise2;
            }

            // resolve 靜態(tài)方法
            static resolve (parameter) {
              // 如果傳入 MyPromise 就直接返回
              if (parameter instanceof MyPromise) {
                return parameter;
              }

              // 轉(zhuǎn)成常規(guī)方式
              return new MyPromise(resolve =>  {
                resolve(parameter);
              });
            }

            // reject 靜態(tài)方法
            static reject (reason) {
              return new MyPromise((resolve, reject) => {
                reject(reason);
              });
            }
          }

          function resolvePromise(promise2, x, resolve, reject{
            // 如果相等了,說(shuō)明return的是自己,拋出類(lèi)型錯(cuò)誤并返回
            if (promise2 === x) {
              return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
            }
            // 判斷x是不是 MyPromise 實(shí)例對(duì)象
            if(x instanceof MyPromise) {
              // 執(zhí)行 x,調(diào)用 then 方法,目的是將其狀態(tài)變?yōu)?nbsp;fulfilled 或者 rejected
              // x.then(value => resolve(value), reason => reject(reason))
              // 簡(jiǎn)化之后
              x.then(resolve, reject)
            } else{
              // 普通值
              resolve(x)
            }
          }

          module.exports = MyPromise;

          到這一步手寫(xiě)部分基本大功告成 ??????

          Promise A+ 測(cè)試

          上面介紹了 Promise A+ 規(guī)范,當(dāng)然我們手寫(xiě)的版本也得符合了這個(gè)規(guī)范才有資格叫 Promise, 不然就只能是偽 Promise 了。

          上文講到了 promises-aplus-tests,現(xiàn)在我們正式開(kāi)箱使用

          1. 安裝一下

          npm install promises-aplus-tests -D

          2. 手寫(xiě)代碼中加入 deferred

          // MyPromise.js

          MyPromise {
            ......
          }

          MyPromise.deferred = function ({
            var result = {};
            result.promise = new MyPromise(function (resolve, reject{
              result.resolve = resolve;
              result.reject = reject;
            });

            return result;
          }
          module.exports = MyPromise;

          3. 配置啟動(dòng)命令

          {
            "name""promise",
            "version""1.0.0",
            "description""my promise",
            "main""MyPromise.js",
            "scripts": {
              "test""promises-aplus-tests MyPromise"
            },
            "author""ITEM",
            "license""ISC",
            "devDependencies": {
              "promises-aplus-tests""^2.1.2"
            }
          }

          開(kāi)啟測(cè)試

          npm run test

          迫不及待了吧 ?? 看看我們的結(jié)果如何,走起 ?????

          fail.gif

          雖然功能上沒(méi)啥問(wèn)題,但是測(cè)試卻失敗了 ??

          針對(duì)提示信息,我翻看了一下 Promise A+ 規(guī)范,發(fā)現(xiàn)我們應(yīng)該是在 2.3.x 上出現(xiàn)了問(wèn)題,這里規(guī)范使用了不同的方式進(jìn)行了 then 的返回值判斷。

          image.png

          自紅線(xiàn)向下的細(xì)節(jié),我們都沒(méi)有處理,這里要求判斷 x 是否為 object 或者 function,滿(mǎn)足則接著判斷 x.then 是否存在,這里可以理解為判斷 x 是否為 promise,這里都功能實(shí)際與我們手寫(xiě)版本中 x instanceof MyPromise 功能相似。

          我們還是按照規(guī)范改造一下 resolvePromise 方法吧

          // MyPromise.js

          function resolvePromise(promise, x, resolve, reject{
            // 如果相等了,說(shuō)明return的是自己,拋出類(lèi)型錯(cuò)誤并返回
            if (promise === x) {
              return reject(new TypeError('The promise and the return value are the same'));
            }

            if (typeof x === 'object' || typeof x === 'function') {
              // x 為 null 直接返回,走后面的邏輯會(huì)報(bào)錯(cuò)
              if (x === null) {
                return resolve(x);
              }

              let then;
              try {
                // 把 x.then 賦值給 then
                then = x.then;
              } catch (error) {
                // 如果取 x.then 的值時(shí)拋出錯(cuò)誤 error ,則以 error 為據(jù)因拒絕 promise
                return reject(error);
              }

              // 如果 then 是函數(shù)
              if (typeof then === 'function') {
                let called = false;
                try {
                  then.call(
                    x, // this 指向 x
                    // 如果 resolvePromise 以值 y 為參數(shù)被調(diào)用,則運(yùn)行 [[Resolve]](promise, y "[Resolve]")
                    y => {
                      // 如果 resolvePromise 和 rejectPromise 均被調(diào)用,
                      // 或者被同一參數(shù)調(diào)用了多次,則優(yōu)先采用首次調(diào)用并忽略剩下的調(diào)用
                      // 實(shí)現(xiàn)這條需要前面加一個(gè)變量 called
                      if (called) return;
                      called = true;
                      resolvePromise(promise, y, resolve, reject);
                    },
                    // 如果 rejectPromise 以據(jù)因 r 為參數(shù)被調(diào)用,則以據(jù)因 r 拒絕 promise
                    r => {
                      if (called) return;
                      called = true;
                      reject(r);
                    });
                } catch (error) {
                  // 如果調(diào)用 then 方法拋出了異常 error:
                  // 如果 resolvePromise 或 rejectPromise 已經(jīng)被調(diào)用,直接返回
                  if (called) return;

                  // 否則以 error 為據(jù)因拒絕 promise
                  reject(error);
                }
              } else {
                // 如果 then 不是函數(shù),以 x 為參數(shù)執(zhí)行 promise
                resolve(x);
              }
            } else {
              // 如果 x 不為對(duì)象或者函數(shù),以 x 為參數(shù)執(zhí)行 promise
              resolve(x);
            }
          }

          改造后啟動(dòng)測(cè)試

          success.gif

          完美通過(guò) ??????

          最終時(shí)刻,如何解釋那道面試題的執(zhí)行結(jié)果

          先用我們自己的 Promise 運(yùn)行一下那道面試題 ??

          // test.js

          const MyPromise = require('./MyPromise.js')

          MyPromise.resolve().then(() => {
            console.log(0);
            return MyPromise.resolve(4);
          }).then((res) => {
            console.log(res)
          })

          MyPromise.resolve().then(() => {
            console.log(1);
          }).then(() => {
            console.log(2);
          }).then(() => {
            console.log(3);
          }).then(() => {
            console.log(5);
          }).then(() =>{
            console.log(6);
          })

          執(zhí)行結(jié)果:0、1、2、4、3、5、6 ??

          這里我們手寫(xiě)版本的 4 并沒(méi)有和 原生 Promise 一樣在 3 后面,而是在 2 后面

          其實(shí)從我們的手寫(xiě)代碼上看,在判斷 then 內(nèi)部函數(shù)執(zhí)行結(jié)果,也就是在這里 ??

          // MyPromise.js

          // 獲取成功回調(diào)函數(shù)的執(zhí)行結(jié)果
          const x = realOnFulfilled(this.value);
          // 傳入 resolvePromise 集中處理
          resolvePromise(promise2, x, resolve, reject);

          面試題中 x 為 MyPromise.resolve(4) 的時(shí)候,在傳入 resolvePromise 方法中會(huì)對(duì) x 的類(lèi)型進(jìn)行判斷時(shí),會(huì)發(fā)現(xiàn)它是一個(gè) Promise,并讓其調(diào)用 then 方法完成狀態(tài)轉(zhuǎn)換。再看 resolvePromis 方法中這一塊判斷邏輯 ??

          if (typeof x === 'object' || typeof x === 'function') {
              // x 為 null 直接返回,走后面的邏輯會(huì)報(bào)錯(cuò)
              if (x === null) {
                return resolve(x);
              }

              let then;
              try {
                // 把 x.then 賦值給 then
                then = x.then;
              } catch (error) {
                // 如果取 x.then 的值時(shí)拋出錯(cuò)誤 error ,則以 error 為據(jù)因拒絕 promise
                return reject(error);
              }

              // 如果 then 是函數(shù)
              if (typeof then === 'function') {
                let called = false;
                try {
                  then.call(
                    x, // this 指向 x
                    // 如果 resolvePromise 以值 y 為參數(shù)被調(diào)用,則運(yùn)行 [[Resolve]](promise, y "[Resolve]")
                    y => {
                      // 如果 resolvePromise 和 rejectPromise 均被調(diào)用,
                      // 或者被同一參數(shù)調(diào)用了多次,則優(yōu)先采用首次調(diào)用并忽略剩下的調(diào)用
                      // 實(shí)現(xiàn)這條需要前面加一個(gè)變量 called
                      if (called) return;
                      called = true;
                      resolvePromise(promise, y, resolve, reject);
                    },
                    // 如果 rejectPromise 以據(jù)因 r 為參數(shù)被調(diào)用,則以據(jù)因 r 拒絕 promise
                    r => {
                      if (called) return;
                      called = true;
                      reject(r);
                    });
                }
                ......

          那么問(wèn)題來(lái)了

          • 為什么我們 Promise A+ 測(cè)試全部通過(guò)的手寫(xiě)代碼,執(zhí)行結(jié)果卻與原生 Promise 不同?
          • 在我們手寫(xiě)代碼使用創(chuàng)建一次微任務(wù)的方式,會(huì)帶來(lái)什么問(wèn)題嗎?

          ES6 中的 Promise 雖然是遵循 Promise A+ 規(guī)范實(shí)現(xiàn)的,但實(shí)際上也 Promise A+ 上做了一些功能擴(kuò)展,例如:Promise.all、Promise.race 等,所以即使都符合 Promise A+ ,執(zhí)行結(jié)果也是可能存在差異的。我們這里更需要思考的是第二個(gè)問(wèn)題,不這么做會(huì)帶來(lái)什么問(wèn)題,也就是加一次微任務(wù)的必要性。

          我嘗試過(guò)很多例子,都沒(méi)有找到相關(guān)例證,我們手寫(xiě)實(shí)現(xiàn)的 Promise 都很好的完成工作,拿到了結(jié)果。我不得不去翻看更多的相關(guān)文章,我發(fā)現(xiàn)有些人會(huì)為了讓執(zhí)行結(jié)果與原生相同,強(qiáng)行去再多加一次微任務(wù),這種做法是很牽強(qiáng)的。

          畢竟實(shí)現(xiàn) Promise 的目的是為了解決異步編程的問(wèn)題,能夠拿到正確的結(jié)果才是最重要的,強(qiáng)行為了符合面試題的輸出順序去多加一次微任務(wù),只能讓手寫(xiě)代碼變的更加復(fù)雜,不好理解。

          在 stackoverflow 上,有一個(gè)類(lèi)似的問(wèn)題 What is the difference between returned Promise?[5]回答中有一個(gè)信息就是

          It only required the execution context stack contains only platform code. 也就相當(dāng)于等待 execution context stack 清空。

          這個(gè)在掘金中的一篇文章 我以為我很懂 Promise,直到我開(kāi)始實(shí)現(xiàn) Promise/A+規(guī)范[6] 也有一段關(guān)于這道面試題的討論

          return Promise.resolve(4),JS 引擎會(huì)安排一個(gè) job(job 是 ECMA 中的概念,等同于微任務(wù)的概念),其回調(diào)目的是讓其狀態(tài)變?yōu)?fulfilled。

          實(shí)際上我們已經(jīng)在 static resolve 創(chuàng)建了一個(gè)新的 MyPromsie,并調(diào)用其 then 方法,創(chuàng)建了一個(gè)微任務(wù)。

          所以,就目前的信息來(lái)說(shuō),兩次微任務(wù)依舊不能證明其必要性,目前的 Promise 日常操作,一次微任務(wù)都是可以滿(mǎn)足。

          大家對(duì)于這個(gè)道面試題有什么想法或者意見(jiàn),趕緊在留言區(qū)告訴我吧,一起探討一下到底是必然還是巧合 ??

          長(zhǎng)文整理不易,記得 點(diǎn)贊 ?? 支持一下哦 ??

          參考資料

          [1]

          Promise 入門(mén): https://es6.ruanyifeng.com/#docs/promise

          [2]

          Promise A+規(guī)范: https://promisesaplus.com/

          [3]

          promises-aplus-tests: https://www.npmjs.com/package/promises-aplus-tests

          [4]

          在 JavaScript 中通過(guò) queueMicrotask() 使用微任務(wù): https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_DOM_API/Microtask_guide

          [5]

          What is the difference between returned Promise?: https://stackoverflow.com/questions/58217603/what-is-the-difference-between-returned-promise

          [6]

          我以為我很懂 Promise,直到我開(kāi)始實(shí)現(xiàn) Promise/A+規(guī)范: https://juejin.cn/post/6937076967283884040

          瀏覽 71
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费视频一区二区三区四 | 大香蕉伊久 | 国产精品传媒在线 | 色婷婷综合在线 | 五月天丁香色婷婷 |