天天看點

線上學習Node.js——Day5之手搓Promise

咱,手搓個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基礎的學習鞏固,以後有空了我還會回來繼續搓的。要去睡覺惹,如果你看這篇學習日志時是晚上的話,祝你晚安哦。

繼續閱讀