天天看点

在线学习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基础的学习巩固,以后有空了我还会回来继续搓的。要去睡觉惹,如果你看这篇学习日志时是晚上的话,祝你晚安哦。

继续阅读