原以為很簡單,結(jié)果這道 Promise 面試題讓我失眠好一會(huì)
來自:掘金,作者:ITEM
鏈接:https://juejin.cn/post/6945319439772434469
點(diǎn)擊上方 前端Q,關(guān)注公眾號(hào)
回復(fù)加群,加入前端Q技術(shù)交流群
先把罪魁禍?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);
})
// 大家先思考一下
這道面試題是無意間在微信群里看到的,據(jù)說是某廠的面試題。一般關(guān)于 Promise 的面試題無非是考察宏微任務(wù)、EventLoop 之類的,當(dāng)我認(rèn)真去分析這道題的時(shí)候,越看越不對(duì)勁,感覺有詐!這是要考察啥?
不管了,先在瀏覽器輸出一下看看 ??
打印結(jié)果:0、1、2、3、4、5、6 ??
這里4怎么跑到3后面去了,不講武德?Why......
在我看來,這道題有兩個(gè) Promise.resolve(),相當(dāng)于創(chuàng)建兩個(gè)狀態(tài)為 fulfilled 的 Promise。
緊隨他們后面的第一個(gè) then 方法會(huì)交替將其執(zhí)行函數(shù)送入微任務(wù)隊(duì)列排隊(duì)執(zhí)行,所以這里的0和1,大家都可以理解,但是接下來執(zhí)行的不是 console.log(res) 而是 console.log(2)。
如果說需要等待 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è)問題,都沒有得到有說服力的結(jié)論,真是百思不得其解!這樣死摳細(xì)節(jié),感覺有點(diǎn)浪費(fèi)時(shí)間,畢竟這種面試題在生產(chǎn)中并不會(huì)出現(xiàn),誰會(huì)去寫這么奇葩的 Promise 代碼, 放棄了,不去想了。
然而??,當(dāng)天晚上夜黑風(fēng)高夜深人靜的時(shí)候,腦海里面依然輪播這道面試題,真的很想知道 Promise 內(nèi)部到底是個(gè)什么邏輯,越想越睡不著~越睡不著越想~
無奈之下,決定參考 Promise A+ 規(guī)范手寫一版 Promise,看看能不能從實(shí)現(xiàn)細(xì)節(jié)中找到蛛絲馬跡。為了方便大家理解,下面我會(huì)利用不同 ?? 來介紹手寫的細(xì)節(jié)和思路。文章最后會(huì)依據(jù)實(shí)現(xiàn)細(xì)節(jié)來探討這道面試題,有手寫經(jīng)驗(yàn)的可以直接跳過手寫 Promise 實(shí)現(xiàn)過程,看最后的結(jié)論。
手寫前需要先了解這些
如果感覺對(duì) Promise 還不太熟悉的就先移步 Promise 入門,稍微做一下知識(shí)預(yù)習(xí),了解一下 Promise 的常規(guī)用法。
什么是宏任務(wù)與微任務(wù)?
我們都知道 Js 是單線程都,但是一些高耗時(shí)操作就帶來了進(jìn)程阻塞問題。為了解決這個(gè)問題,Js 有兩種任務(wù)的執(zhí)行模式:同步模式(Synchronous)和異步模式(Asynchronous)。
在異步模式下,創(chuàng)建異步任務(wù)主要分為宏任務(wù)與微任務(wù)兩種。ES6 規(guī)范中,宏任務(wù)(Macrotask) 稱為 Task, 微任務(wù)(Microtask) 稱為 Jobs。宏任務(wù)是由宿主(瀏覽器、Node)發(fā)起的,而微任務(wù)由 JS 自身發(fā)起。
宏任務(wù)與微任務(wù)的幾種創(chuàng)建方式 ??
| 宏任務(wù)(Macrotask) | 微任務(wù)(Microtask) |
|---|---|
| setTimeout | requestAnimationFrame(有爭議) |
| setInterval | MutationObserver(瀏覽器環(huán)境) |
| MessageChannel | Promise.[ 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è)過程中創(chuàng)建了微任務(wù)并進(jìn)入了微任務(wù)隊(duì)列,第一個(gè) script 同步代碼執(zhí)行完之后,會(huì)首先去清空微任務(wù)隊(duì)列,再去開啟第二個(gè) script 代碼塊的執(zhí)行。所以這里應(yīng)該就可以理解 script(整體代碼塊)為什么會(huì)是宏任務(wù)。
什么是 EventLoop ?
先來看個(gè)圖
-
判斷宏任務(wù)隊(duì)列是否為空
-
不空 --> 執(zhí)行最早進(jìn)入隊(duì)列的任務(wù) --> 執(zhí)行下一步 -
空 --> 執(zhí)行下一步 -
判斷微任務(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ī)范了解一下,這里不贅述。
檢驗(yàn)一份手寫 Promise 靠不靠譜,通過 Promise A+ 規(guī)范自然是基本要求,這里我們可以借助 promises-aplus-tests 來檢測我們的代碼是否符合規(guī)范,后面我會(huì)講到如何使用它。
手寫開始
很多手寫版本都是使用 setTimeout 去做異步處理,但是 setTimeout 屬于宏任務(wù),這與 Promise 是個(gè)微任務(wù)相矛盾,所以我打算選擇一種創(chuàng)建微任務(wù)的方式去實(shí)現(xiàn)我們的手寫代碼。
這里我們有幾種選擇,一種就是 Promise A+ 規(guī)范中也提到的,process.nextTick( Node 端 ) 與MutationObserver( 瀏覽器端 ),考慮到利用這兩種方式需要做環(huán)境判斷,所以在這里我們就推薦另外一種創(chuàng)建微任務(wù)的方式 queueMicrotask,了解更多 --> 在 JavaScript 中通過 queueMicrotask() 使用微任務(wù);
一、Promise 核心邏輯實(shí)現(xià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
我們來分析一下基本原理:
Promise 是一個(gè)類,在執(zhí)行這個(gè)類的時(shí)候會(huì)傳入一個(gè)執(zhí)行器,這個(gè)執(zhí)行器會(huì)立即執(zhí)行 Promise 會(huì)有三種狀態(tài)
Pending 等待 Fulfilled 完成 Rejected 失敗 狀態(tài)只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但發(fā)生改變便不可二次修改; Promise 中使用 resolve 和 reject 兩個(gè)函數(shù)來更改狀態(tài); then 方法內(nèi)部做但事情就是狀態(tài)判斷
如果狀態(tài)是成功,調(diào)用成功回調(diào)函數(shù) 如果狀態(tài)是失敗,調(diào)用失敗回調(diào)函數(shù)
下面開始實(shí)現(xiàn):
1. 新建 MyPromise 類,傳入執(zhí)行器 executor
// 新建 MyPromise.js
// 新建 MyPromise 類
class MyPromise {
constructor(executor){
// executor 是一個(gè)執(zhí)行器,進(jìn)入會(huì)立即執(zhí)行
executor()
}
}
2. executor 傳入 resolve 和 reject 方法
// MyPromise.js
// 新建 MyPromise 類
class MyPromise {
constructor(executor){
// executor 是一個(gè)執(zhí)行器,進(jìn)入會(huì)立即執(zhí)行
// 并傳入resolve和reject方法
executor(this.resolve, this.reject)
}
// resolve和reject為什么要用箭頭函數(shù)?
// 如果直接調(diào)用的話,普通函數(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 類
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)用的話,普通函數(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 的簡單實(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 類
// MyPromise.js
module.exports = MyPromise;
看一下我們目前實(shí)現(xiàn)的完整代碼??
// MyPromise.js
// 先定義三個(gè)常量表示狀態(tài)
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 新建 MyPromise 類
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)用的話,普通函數(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
使用我的手寫代碼執(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 類中加入異步邏輯
上面還沒有經(jīng)過異步處理,如果有異步邏輯加如來會(huì)帶來一些問題,例如:
// 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)
})
// 沒有打印信息!!!
分析原因:
主線程代碼立即執(zhí)行,setTimeout 是異步代碼,then 會(huì)馬上執(zhí)行,這個(gè)時(shí)候判斷 Promise 狀態(tài),狀態(tài)是 Pending,然而之前并沒有判斷等待這個(gè)狀態(tài)
這里就需要我們處理一下 Pending 狀態(tài),我們改造一下之前的代碼 ??
1. 緩存成功與失敗回調(diào)
// MyPromise.js
// MyPromise 類中新增
// 存儲(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ǔ)起來
// 等到執(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)
}
}
我們再執(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)可以簡單處理異步問題了??
三、實(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 類中新增兩個(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ǔ)起來
// 等待后續(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)拿出來執(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)拿出來執(zhí)行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}
再來運(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) => {
// 目前這里只處理同步的問題
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)
})
用目前的手寫代碼運(yùn)行的時(shí)候會(huì)報(bào)錯(cuò) ?? 無法鏈?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))
// 簡化之后
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)類型錯(cuò)誤
100
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
我們在 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) {
// 如果相等了,說明return的是自己,拋出類型錯(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>
哈哈,搞定 ?? 開始下一步
六、捕獲錯(cuò)誤及 then 鏈?zhǔn)秸{(diào)用其他狀態(tài)代碼補(bǔ)充
目前還缺少重要的一個(gè)環(huán)節(jié),就是我們的錯(cuò)誤捕獲還沒有處理
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,通過 ??
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ǔ)起來
// 等到執(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)容包括:
增加異步狀態(tài)下的鏈?zhǔn)秸{(diào)用 增加回調(diào)函數(shù)執(zhí)行結(jié)果的判斷 增加識(shí)別 Promise 是否返回自己 增加錯(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ǔ)起來
// 等到執(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
寫到這里,麻雀版的 Promise 基本完成了,鼓掌 ??????
九、實(shí)現(xiàn) resolve 與 reject 的靜態(tài)調(diào)用
就像開頭掛的那道面試題使用 return Promise.resolve 來返回一個(gè) Promise 對(duì)象,我們用現(xiàn)在的手寫代碼嘗試一下
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 的用法,我們都要去支持,接下來我們來實(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);
});
}
}
這樣我們再測試上面的 ?? 就不會(huì)有問題啦
執(zhí)行結(jié)果 ??
0
4
到這里手寫工作就基本完成了,前面主要為了方便理解,所以有一些冗余代碼,我規(guī)整一下
// MyPromise.js
// 先定義三個(gè)常量表示狀態(tài)
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 新建 MyPromise 類
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)拿出來執(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)拿出來執(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ǔ)起來
// 等到執(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) {
// 如果相等了,說明return的是自己,拋出類型錯(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))
// 簡化之后
x.then(resolve, reject)
} else{
// 普通值
resolve(x)
}
}
module.exports = MyPromise;
到這一步手寫部分基本大功告成 ??????
Promise A+ 測試
上面介紹了 Promise A+ 規(guī)范,當(dāng)然我們手寫的版本也得符合了這個(gè)規(guī)范才有資格叫 Promise, 不然就只能是偽 Promise 了。
上文講到了 promises-aplus-tests,現(xiàn)在我們正式開箱使用
1. 安裝一下
npm install promises-aplus-tests -D
2. 手寫代碼中加入 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"
}
}
開啟測試
npm run test
迫不及待了吧 ?? 看看我們的結(jié)果如何,走起 ?????
雖然功能上沒啥問題,但是測試卻失敗了 ??
針對(duì)提示信息,我翻看了一下 Promise A+ 規(guī)范,發(fā)現(xiàn)我們應(yīng)該是在 2.3.x 上出現(xiàn)了問題,這里規(guī)范使用了不同的方式進(jìn)行了 then 的返回值判斷。
自紅線向下的細(xì)節(jié),我們都沒有處理,這里要求判斷 x 是否為 object 或者 function,滿足則接著判斷 x.then 是否存在,這里可以理解為判斷 x 是否為 promise,這里都功能實(shí)際與我們手寫版本中 x instanceof MyPromise 功能相似。
我們還是按照規(guī)范改造一下 resolvePromise 方法吧
// MyPromise.js
function resolvePromise(promise, x, resolve, reject) {
// 如果相等了,說明return的是自己,拋出類型錯(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)
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)測試
完美通過 ??????
最終時(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 ??
這里我們手寫版本的 4 并沒有和 原生 Promise 一樣在 3 后面,而是在 2 后面
其實(shí)從我們的手寫代碼上看,在判斷 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 的類型進(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)
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);
});
}
......
那么問題來了
-
為什么我們 Promise A+ 測試全部通過的手寫代碼,執(zhí)行結(jié)果卻與原生 Promise 不同? -
在我們手寫代碼使用創(chuàng)建一次微任務(wù)的方式,會(huì)帶來什么問題嗎?
ES6 中的 Promise 雖然是遵循 Promise A+ 規(guī)范實(shí)現(xiàn)的,但實(shí)際上也 Promise A+ 上做了一些功能擴(kuò)展,例如:Promise.all、Promise.race 等,所以即使都符合 Promise A+ ,執(zhí)行結(jié)果也是可能存在差異的。我們這里更需要思考的是第二個(gè)問題,不這么做會(huì)帶來什么問題,也就是加一次微任務(wù)的必要性。
我嘗試過很多例子,都沒有找到相關(guān)例證,我們手寫實(shí)現(xiàn)的 Promise 都很好的完成工作,拿到了結(jié)果。我不得不去翻看更多的相關(guān)文章,我發(fā)現(xiàn)有些人會(huì)為了讓執(zhí)行結(jié)果與原生相同,強(qiáng)行去再多加一次微任務(wù),這種做法是很牽強(qiáng)的。
畢竟實(shí)現(xiàn) Promise 的目的是為了解決異步編程的問題,能夠拿到正確的結(jié)果才是最重要的,強(qiáng)行為了符合面試題的輸出順序去多加一次微任務(wù),只能讓手寫代碼變的更加復(fù)雜,不好理解。
在 stackoverflow 上,有一個(gè)類似的問題 What is the difference between returned Promise? 回答中有一個(gè)信息就是
It only required the execution context stack contains only platform code.也就相當(dāng)于等待execution context stack清空。
這個(gè)在掘金中的一篇文章 我以為我很懂Promise,直到我開始實(shí)現(xiàn)Promise/A+規(guī)范 也有一段關(guān)于這道面試題的討論
return Promise.resolve(4),JS引擎會(huì)安排一個(gè)job(job是 ECMA 中的概念,等同于微任務(wù)的概念),其回調(diào)目的是讓其狀態(tài)變?yōu)閒ulfilled。
實(shí)際上我們已經(jīng)在 static resolve 創(chuàng)建了一個(gè)新的 MyPromsie,并調(diào)用其 then 方法,創(chuàng)建了一個(gè)微任務(wù)。
所以,就目前的信息來說,兩次微任務(wù)依舊不能證明其必要性,目前的 Promise 日常操作,一次微任務(wù)都是可以滿足。
大家對(duì)于這個(gè)道面試題有什么想法或者意見,趕緊在留言區(qū)告訴我吧,一起探討一下到底是必然還是巧合??
長文整理不易,記得 點(diǎn)贊 ?? 支持一下哦 ??
往期推薦
最后
歡迎加我微信,拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...
