实现你的promise

104 阅读3分钟

「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战

已经了解了promiseA+规范,这就开始用JS实现promsie 的核心逻辑。在实现(模仿)之前,我们先看看正牌的Promise的基本使用。

如果想拆开地狱回调,就应该把下一层的promise作为then的解决回调的返回值,这样就能转为链式。

如此一来,看起来会舒服很多。并且只需要在最后catch一下即可。

创建实例的一般方式就是, new 一个。需要一个函数作为参数,这个函数的前两个参数是 解决和拒绝的回调。函数的主体逻辑就是期约的执行逻辑。包含何时解决/拒绝

let p = new Promise((res,rej)=>{
        setTimeout(() => {
            res(1)
        }, 1);
        if(2 > 3){
        rej('')
    })
 

then 方法的使用,任何期约实例上都可调用,then方法可调用多次, 有两个可选参数。并且可以在then返回的实例上再调用catch。

p.then(()=>{
        console.log(p)
        // return Promise.resolve(2)
    }, ()=> {}).then((n)=>{console.log(n,1)})
    p.then(()=>{
        return 2
    }).then((n)=>{console.log(n,2)}).catch(()=> {})

实现自己的promise

image.png

状态定义及转变

先实现状态定义,默认是pending。 然后就是执行传入的回调,这个回调函数有两个可选参数(期望是函数),这两个函数一旦调用就会改变期约的状态和得到最终结果。下面用两个私有方法将其抽离出来。

class Appoint{
    /* new promise 需传入一个函数,每个实例须有自己的状态,预备一个解决结果 */
    constructor(handle=(res,rej)=> {}){
        this['[[PromiseState]]'] = 'pending'
        this['[[PromiseResult]]'] = undefined
         handle(this.#resolve, this.#reject)

    }
    /* 调用静态方法转变状态,改变结果 */
    #resolve(val) {

        this["[[PromiseState]]"] = 'fullfilled'
        this["[[PromiseResult]]"] = val
    }
    #reject(err){

        this["[[PromiseState]]"] = 'rejected'
        this["[[PromiseResult]]"] = err
    }
}

这样,状态定义和转变就好了。 然后执行的时候就发现一个问题, 那就是在调用resolve或者reject的时候发现,this不对,确实不对,上下文环境已经改变。我们需要处理一下。

handle(this.#resolve.bind(this), this.#reject.bind(this))

实现then方法

then方法应该在状态变化之后立即执行,then方法也有两个可选参数,分别在拒绝和兑现之后调用。 因此,我们应该在执行私有方法#resolve或#reject之后再执行对应的回调,也就是需要把回调写在改变状态的私有方法里。

image.png

然后解决几个问题:

发布订阅

then可以调用多次,需要把全部的then的参数保存起来,调用的时候依次调,注意可以有多个then但是每个then只能调用一次。这里直接for循环 然后清空数组,如果按照队列的规范应该一边出队(shift)一边执行。

image.png

微任务

解决then的回调需要在当前执行上下文栈空时执行, 而不是立即执行。

之前的实现需要我们传入一个异步逻辑才能保证其逻辑。如果我们写一个同步的代码,那么可能会立即执行then的逻辑,但是此时,then的回调还没收集。 这里使用mutationObserver来实现微任务。 也可以直接使用 ququeMicrotask

image.png

链式

解决 then的返回值应该是一个promise 并且状态之前的一样, 返回的新的promise实例的理由/值 由then的返回值决定。

如果是一个非Promise值,那么就以此作为解决值。 如果是一个promise则返回的实例的状态和值应当和其相同。

传递返回值,所以先拿到返回值,然后把它作为新期约的结果进行解决, 并且将新期约的创建作为一个当前then的回调函数 ,这样就能在当前then回调执行完之后,立即构建这个期约

 return new Appoint((res,rej)=> {
            let resovleFn = function(val){
                   let  result = resolve && resolve(val)
                if( result instanceof Appoint){
                    result.then(resolve)
                }else {
                    res(result)
                }
            }
            this.onResolveQueue.push( resovleFn)

            let  rejecteFn = function(val){
                    rejecte && rejecte(val)
                    rej(val)
            }
           this.onRejectQueue.push(rejecteFn)
        })

至此,我们的Appoint的核心逻辑就完成了。