咱,手搓個Promise的mini版複制品玩玩?(老師說這樣子就很像工作後搞開發的感覺捏)
目錄
一、手搓計劃
二、開整
定義個狀态對象
定義一個叫MyPromise的class
定義函數
建立一個變量,用來存儲Promise的結果
建立一個變量來記錄Promise的狀态
私有的resolve() 用來存儲成功的資料,私有的reject() 用來存儲拒絕的資料
禁止值被重複修改
添加一個用來讀取資料的then方法
三、問題出現了!出大事,讀不到異步的資料!
分析
解決辦法
代碼實作
四、繼續開整
把孩子加入微任務隊列裡
讓MyPromise可以反複讀取資料
實作鍊式調用
一、手搓計劃
首先是,定義類的思路:
1、先把功能都分析清楚了,在動手
2、寫一點想一點,走一步看一步
那!讓我們來盤一盤Promise都需要實作些什麼叭:
1、能根據成功失敗傳回不同的值,且方法是私有的
2、有狀态 (pending ; fulfilled ; rejected )
3、要克隆方法then、catch、finally
4、狀态隻能更改一次
然後!咱們捋一下初步的思路:
- 定義個狀态對象
- 定義一個叫MyPromise的class
- 建立一個變量,用來存儲Promise的結果
- 建立一個變量來記錄Promise的狀态
- 定義個函數,接受一個 執行器(executor) 作為參數
- 定義resolve方法、reject方法,把這兩個執行個體方法 作為參數傳給了執行器executor
- 禁止值被重複修改
- 添加一個用來讀取資料的then方法
- 實作異步取值
二、開整
定義個狀态對象
const PROMISE_STATE = {
PENDING:0,
FULFILLED:1,
REJECTED:2
}
定義一個叫MyPromise的class
class MyPromise{ }
定義函數
(執行器 => 形參)
定義一個resolve方法、reject方法,把這兩個執行個體方法 作為參數傳給了執行器executor
"#" —— 把這兩個變成私有方法,隻有在内部可以通路,但是傳出來是沒有問題的,但是無法通過mp.resolve()通路到。
用bind()鎖死,使this一直指向執行個體對象
class MyPromise{
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) ) }
}
建立一個變量,用來存儲Promise的結果
class MyPromise{
#result
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
}
建立一個變量來記錄Promise的狀态
class MyPromise{
#result
#state = PROMISE_STATE.PENDING
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
}
私有的resolve() 用來存儲成功的資料,私有的reject() 用來存儲拒絕的資料
class MyPromise{
#result
#state = PROMISE_STATE.PENDING
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
#resolve(value){ }
#reject(reason){ }
}
禁止值被重複修改
class MyPromise{
#result
#state = PROMISE_STATE.PENDING
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
#resolve(value){
//如果state不等于0,說明值已經被修改 函數直接傳回
if(this.#state !== PROMISE_STATE.PENDING) return
this.#result = value
this.#state = PROMISE_STATE.FULFILLED //資料填充成功
}
#reject(reason){ }
}
添加一個用來讀取資料的then方法
class MyPromise{
#result
#state = PROMISE_STATE.PENDING
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
#resolve(value){
//如果state不等于0,說明值已經被修改 函數直接傳回
if(this.#state !== PROMISE_STATE.PENDING) return
this.#result = value
this.#state = PROMISE_STATE.FULFILLED //資料填充成功
}
#reject(reason){ }
then(onFulfilled,onRejected){
if(this.#state === PROMISE_STATE.FULFILLED){
onFulfilled(this.#result)
}
}
}
三、問題出現了!出大事,讀不到異步的資料!
此時我們如果調用立即可以傳輸資料的result時:
const mp = new MyPromise( (resolve,reject) => {
resolve("勺子最酷酷")
resolve("果叽最酷酷")
})
//console.log(mp);
mp.then( (result) => {
console.log("讀取資料",result);
})
//輸出:讀取資料 勺子最酷酷
這時候,貌似一切正常,風平浪靜,靜如處子,子子孫孫,孫孫子子。。。
但是!當我們用setTimeout模拟一下異步延時傳輸資料的情況:
const mp = new MyPromise( (resolve,reject) => {
setTimeout( () => {
resolve("勺子最酷酷")
resolve("果叽最酷酷")
},0)
})
//console.log(mp);
mp.then( (result) => {
console.log("讀取資料",result);
})
//沒有輸出了!
此時,沒有輸出了!也就是說,咱沒有實作異步時讀取資料!!
分析
目前來說,then隻能讀取已經存儲進Promise的資料,而不能讀取異步存儲的資料,讀資料實際上就是調onFulfilled回調函數。
目前可以調回調函數的地方:
1、then裡面可以通過onFulfilled回調函數傳回資料
2、#resolve
當resolve執行時,說明資料已經進來了,需要調用then的回調函數onFulfilled,而onFulfilled是then的參數,resolve看不到!!!
解決辦法
我們在MyPromise裡面添加了一個私有屬性:callback,用來存儲回調函數,當我們調用then讀取資料的時候,我們做了一個判斷:
當狀态為pending時 ———— 表示資料還沒有被傳進MyPromise存儲
此時我們就把onFulfilled函數指派為我們對象的callback屬性的值
this.#callback = inFulfilled
當資料進入MyPromise時,#resolve就會執行了
然後就會把value指派給result,把state修改為fulfilled
this.#result = value
this.#state = PROMISE_STATE.FULFILLED
然後就會去調this.#callback,也就是剛才我們then傳過來的那個函數onFulfilled
然後就把result傳給了我們的回調函數
this.#callback(this.#result)
然後我們就能在mp.then 的時候,傳回異步的值了
代碼實作
const PROMISE_STATE = {
PENDING:0,
FULFILLED:1,
REJECTED:2
}
class MyPromise{
#result
#state = PROMISE_STATE.PENDING
//建立一個變量來存儲回調函數
#callback
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
#resolve(value){
if(this.#state !== PROMISE_STATE.PENDING) return
this.#result = value
this.#state = PROMISE_STATE.FULFILLED //資料填充成功
//這樣就能通路到回調函數了,把result作為參數傳進去
//這樣當我們調用resolve的時候,這個callback也就執行了,然後this.result的這個參數就傳給了callback
this.#callback && this.#callback(this.#result) //做一個判斷,如果callback有,咱才調用
}
#reject(reason){ }
then(onFulfilled, onRejected){
if(this.#state === PROMISE_STATE.PENDING){
//進入判斷,說明資料還沒有進入Promise,将回調函數設定為callback的值
this.#callback = onFulfilled
}
else if(this.#state === PROMISE_STATE.FULFILLED){
onFulfilled(this.#result)
}
}
}
const mp = new MyPromise( (resolve,reject) => {
setTimeout( () => {
resolve("勺子最酷酷")
},520)
})
mp.then( (result) => {
console.log("讀取資料",result);
})
這個bug目前是搞定了~這裡我就一坨的寫出來了,添加的地方都用注釋标出來惹,這就是目前寫到的完整版嗷
四、繼續開整
在繼續搓之前,咱們盤一盤第二階段的思路。
- 首先,人家原版Promise裡的執行個體函數都是在微任務隊列(VIP通道)排隊的捏,咱的娃都是被直接調用的,沒有給他們快速通行證(遊樂場裡VIP通道通行的票)
- 然後,原版Promise是可以反複讀取資料的,但咱不行
- 再然後,原版Promose可以鍊式調用,可以連續then,這咱也不行
把孩子加入微任務隊列裡
衆所周知,普通遊客想要進VIP快速通道,需要用queueMicrotask()方法捏
//在#resolve裡哈,我就寫個局部的更改
queueMicrotask( () => {//把孩子加到微任務隊列裡
this.#callback && this.#callback(this.#result)
})
//在then裡哈,我也是就寫個局部的更改
then(onFulfilled, onRejected){//把孩子加到微任務隊列裡
if(this.#state === PROMISE_STATE.PENDING){
this.#callback = onFulfilled
}
}
讓MyPromise可以反複讀取資料
咱先看看原汁原味的Promise怎麼搞的哈
const p = Promise.resolve("勺子美滋滋")
p.then(r => console.log("第一次讀",r))
p.then(r => console.log("第二次讀",r))
/*
輸出:
第一次讀 勺子美滋滋
第二次讀 勺子美滋滋
*/
但是咱的,就不能重複讀取
const mp = new MyPromise( (resolve,reject) => {
setTimeout( () => {
resolve("勺子最酷酷")
},520)
})
mp.then( (result) => {
console.log("讀取資料第一次",result);
})
mp.then( (result) => {
console.log("讀取資料第二次",result);
})
//傳回:讀取資料第二次 勺子最酷酷
第二次給callback的指派,把第一次的覆寫了, 是以隻能顯示第二次的
解決方法:
由于我們的回調函數可能有多個,是以我們使用數組來存儲回調函數
//局部戰場哈
#callback = []
//在resolve裡
queueMicrotask( () => {
//this.#callback && this.#callback(this.#result) 沒用了,殘忍注掉
//調用callback中的所有函數,先周遊,然後調函數
this.#callback.forEach(cb => {
cb()
})
})
//在then裡
if(this.#state === PROMISE_STATE.PENDING){
//this.#callback = onFulfilled - 因為callback變數組了,這條也沒用,殘忍咔嚓
//往數組裡加一個東西,把包裝後的函數直接作為參數傳入
//當箭頭函數調用的時候,onFulfilled也就執行了
this.#callback.push(() => {
onFulfilled(this.#result)
})
}
實作鍊式調用
咱就是說,能實作連續then的前提是,前一個兄弟傳回的要是一個新的Promise,才能再then,但咱現在的傳回值它不是捏
let result = mp.then( (result) => {})
console.log(result);
// 傳回:undefined
解決方法:
我們需要讓then傳回的是一個Promise,是以對then就有了以下的更改
then(onFulfilled, onRejected){
return new MyPromise((resole,reject) => {
if(this.#state === PROMISE_STATE.PENDING){
this.#callback.push(() => {
onFulfilled(this.#result)
})
}
else if(this.#state === PROMISE_STATE.FULFILLED){
queueMicrotask(() => {
onFulfilled(this.#result)
})
}
})
}
但是!!但是如果我們是這麼使得它then後傳回的是Promise的話,它state就一直都是pending,也就是說這個裡也一直沒有資料,是以,咱需要給這個return new MyPromise裡注入資料,通過它的resolve和reject。
那麼問題來了,誰将成為then傳回的新MyPromise中的資料?
答:then中回調函數的傳回值會成為新Promise的資料,也就是onFulfilled的傳回值會成為新資料
解決方法:
把回調函數的傳回值,作為resolve的參數,傳給新的Promise
const PROMISE_STATE = {
PENDING:0,
FULFILLED:1,
REJECTED:2
}
class MyPromise{
#result
#state = PROMISE_STATE.PENDING
#callback = []
constructor(executor){
executor(this.#resolve.bind(this),this.#reject.bind(this) )
}
#resolve(value){
if(this.#state !== PROMISE_STATE.PENDING) return
this.#result = value
this.#state = PROMISE_STATE.FULFILLED
queueMicrotask( () => {
this.#callback.forEach(cb => {
cb()
})
})
}
#reject(reason){ }
then(onFulfilled, onRejected){
return new MyPromise((resolve,reject) => {
if(this.#state === PROMISE_STATE.PENDING){
this.#callback.push(() => {
resolve(onFulfilled(this.#result))//把回調函數的傳回值,作為resolve的參數,傳給新的Promise
})
}
else if(this.#state === PROMISE_STATE.FULFILLED){
queueMicrotask(() => {
resolve(onFulfilled(this.#result)) //把回調函數的傳回值,作為resolve的參數,傳給新的Promise
})
}
})
}
}
const mp = new MyPromise( (resolve,reject) => {
setTimeout( () => {
resolve("勺子最酷酷")
},520)
})
mp.then( (result) => {
console.log("讀取資料第一次",result);
return "勺子有點困"
})
.then(r => {
console.log("讀取資料第二次",r);
return "勺子想睡覺"
})
.then(r => {
console.log("讀取資料第三次",r);
})
這就是目前最新版本的全部代碼了,增加改進的兩個地方寫了注釋哈。
以上就是勺子手搓Promise複制品的現階段實作情況了,對勺子來說,還是有一定的難度的,但是通過這次學習,大大大的加強了我對Promise的了解和對js基礎的學習鞏固,以後有空了我還會回來繼續搓的。要去睡覺惹,如果你看這篇學習日志時是晚上的話,祝你晚安哦。