抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

上次手写Promise留下了很多问题,这次全部解决一下

支持异步

先来看异步的问题,正常Promise执行是异步的,晚于同步执行,现在的执行顺序:

const p = new Promise((resolve, reject) => {
    resolve(666)
}).then(res=>{
    console.log(res);
})

console.log('同步代码执行啦!')
/* output:

666
同步代码执行啦!

*/

正常来说传递到 then() 中的函数被置入到一个微任务队列中,而不是立即执行,这意味着它是在 JavaScript 事件队列的所有运行时结束了,且事件队列被清空之后,才开始执行。也就是说666应该晚于同步代码再输出,这时候将onFulfilled和onRejected函数放到异步队列里面,包一个setTimeout就好了

then(onFulfilled, onRejected) {
    // ...
    if (this.status === Promise.FULFILLED) {
        setTimeout(()=>{
            try{
                onFulfilled(this.value)
            }catch (reason){
                onRejected(reason)
            }
        })
    }
    if (this.status === Promise.REJECTED) {
        setTimeout(()=>{
            try{
                onRejected(this.value)
            }catch (reason){
                onRejected(reason)
            }
        })
    }
}

再看输出:

/*
output:
同步代码执行啦!
666
*/

多个处理函数

上次说到处理函数每个状态只对应一个处理函数,也就是then只能执行一次,resolve状态只对应一个处理函数,具体是怎么样的呢?下面详细说一下——

const p = new Promise((resolve, reject) => {
    console.log("我是executer")
    setTimeout(()=>resolve('解决啦!'))
})
p.then(res => {
    console.log(res,'第一个then')
})
p.then(res => {
    console.log(res,'第二个then')
})

/*
output:

我是executer
解决啦! 第二个then

*/

没有少打,真的是只输出了第二个then,第一个then被谁吃了呢?

executer里的代码,resolve是先被一个函数裹着放到异步队列里了,

const p = new Promise((resolve, reject) => {
    console.log("我是executer")
    setTimeout(() => resolve('解决啦!'))
})

看下面的then函数本身,全是同步的,同步肯定先于异步执行,先看第一个then执行的时候:

then(onFulfilled, onRejected) {
    // ... 节省篇幅省略了无关代码
    if (this.status === Promise.PENDING) {
        this.successCallback = () => {
            onFulfilled(this.value)
        }

        this.failCallback = () => {
            onRejected(this.value)
        }

    }
	// ...
}

因为resolve被裹着不执行,这时候状态没有改变还是pending,所以第一个then跑完了就光把对应状态的处理函数函数暂存了起来,好了结束了。

继续看第二个then,这个then执行也是同步的,被resolve裹着的函数就等着同步执行完了再执行呢,所以现在的状态还是pending,他同第一个then一样,把当前的处理函数暂存起来,但是注意,这次暂存把上一个then的callback给覆盖了,继续——

同步代码终于执行完了,executer里的setTimeout开始执行,resolve!调用类里的resolve方法:

resolve(result) {
    if (this.status === Promise.PENDING) {
        this.value = result
        this.status = Promise.FULFILLED
        this.successCallback()
    }
}

自然就是执行自己暂存的callbalck,但是这个只有第二个callback,第一个已经被覆盖了。

所以,怎么办嘞?

答案就是放到数组里!你可以叫他“任务队列”

两个状态的任务队列:

constructor(executer) {
    // ...
    this.successCallback = []
    this.failCallback = []
    // ...
}

then方法内,Promise还处在pending状态时添加到任务队列:

then(onFulfilled, onRejected) {
    if (this.status === Promise.PENDING) {
        this.successCallback.push(()=>{
            onFulfilled(this.value)
        })

        this.failCallback.push(()=>{
            onFulfilled(this.value)
        })
    }
    // ...
}

resolve时执行成功时的回调函数,reject执行失败时的回调函数

resolve(result) {
    if (this.status === Promise.PENDING) {
        // ...
        this.successCallback.forEach(fn=>{
            fn()
        })
    }
}
reject(reason) {
    if (this.status === Promise.PENDING) {
        // ...
        this.failCallback.forEach(fn=>{
            fn()
        })
    }
}

现在跑一下:

/* output:
我是executer
解决啦! 第一个then
解决啦! 第二个then
*/

成功解决!

实现链式调用

重头戏来了,开始完善一下then方法。

先看一下现在的状态:

const p = new Promise((resolve, reject) => {
    console.log("我是executer")
    setTimeout(() => resolve('解决啦!'))
})
p.then(res => {
    console.log(res, '第一个then')
    return 666
}).then(res => {
    console.log(res, '第二个then')
})
/* output:

我是executer
file:///D:/MYSOURSE/project/nodejs/promise.js:80
}).then(res => {
  ^

TypeError: Cannot read property 'then' of undefined
    at file:///D:/MYSOURSE/project/nodejs/promise.js:80:3
    at ModuleJob.run (internal/modules/esm/module_job.js:146:23)
    at async Loader.import (internal/modules/esm/loader.js:165:24)
    at async Object.loadESM (internal/process/esm_loader.js:68:5)

*/

代码不变,再看原版Promise:

/* output:

我是executer
解决啦! 第一个then
666 第二个then

*/

可以看到第二个then实际上是根据上一个then的返回值来作为参数的(不返回Promise的情况下),现在我们的代码,then方法啥也不返回,所以自然是undefined,所以应该返回个Promise,只有它才能调用then方法,具体写法:

then(onFulfilled, onRejected) {
    // ...
    return new Promise((resolve, reject) => {
        if (this.status === Promise.PENDING) {
            this.successCallback.push(() => {
                try {
                    const result = onFulfilled(this.value)
                    if (result instanceof Promise) {
                        result.then(resolve, reject)
                    } else {
                        resolve(result)
                    }
                } catch (e) {
                    reject(e)
                }
            })
            // failCallback 代码类似,省略
        }
        // ...
    })
}

因为要接收上一个处理函数的返回值给下一个Promise,所以直接写在了新创建的Promise的executer函数中,巧妙地对接了两个Promise

执行到当前then的时候,先考虑异步情况,当前状态pending,添加到成功状态后的任务队列中,当resolve时,执行callback里的代码,注意代码中的this指向当前Promise,过程如下:

  1. 执行then方法传来的成功回调函数,将当前executer中resolve的值传进去
  2. 执行过后的返回值(成功回调函数中return的值)赋给result
  3. 判断result是不是个Promise,是的话,继续递归他,否则resolve,这里的resolve是新Promise的resolve,这时新的Promise也就fulfilled了

如果上一个then返回的是非Promise,也就是非异步任务,新Promise直接resolve了,如果是异步任务,还要执行一遍.then为的就是把他加进任务队列,再看他是不是异步任务,最终总会变成非异步任务

返回约束

then的返回的promise不能是then相同的Promise,下面是原生Promise的示例将产生错误

const p = new Promise((resolve, reject) => {
    console.log("我是executer")
    setTimeout(() => resolve('解决啦!'))
})
const promise = p.then(res => {
    console.log(res, '第一个then')
    return promise
})
/*
output:

我是executer
解决啦! 第一个then
(node:20848) UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise>

*/

then的返回值是自己本身了,这样会造成死循环,直接在then的返回值加上一句判断就好了,为了方便维护,我们将公共代码抽离成parse方法:

// 公共部分
parse(promise, result, resolve, reject) {
    // 新Promise和then的返回值
    if (promise === result) {
        throw new TypeError("Chaining cycle detected for promise");
    }
    try {
        if (result instanceof Promise) {
            result.then(resolve, reject);
        } else {
            resolve(result);
        }
    } catch (error) {
        reject(error);
    }
}

抽离后的then方法:

const promise = new Promise((resolve, reject) => {
        if (this.status === Promise.PENDING) {
            this.successCallback.push(() => {
                    this.parse(promise, onFulfilled(this.value), resolve, reject);
                });
            this.failCallback.push(() => {
                this.parse(promise, onRejected(this.value), resolve, reject);
            })
        }
        if (this.status === Promise.FULFILLED) {
            setTimeout(() => {
                this.parse(promise, onFulfilled(this.value), resolve, reject);
            });
        }
        if (this.status === Promise.REJECTED) {
            setTimeout(() => {
                this.parse(promise, onRejected(this.value), resolve, reject);
            });
        }
    });
    return promise;
}

现在再执行就会报错了,成功解决!

这里有个小细节,new出来的新Promise赋值给promise,正常来说新promise里的executer是拿不到新创建的Promise对象的,会爆出ReferenceError: Cannot access ‘promise’ before initialization

具体看

关于setInterval的思考_孙泽辉的博客-CSDN博客

到这里整个Promise基本就已经实现了,剩下一些自带函数我们留着下回实现

评论