上次手写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,过程如下:
- 执行then方法传来的成功回调函数,将当前executer中resolve的值传进去
- 执行过后的返回值(成功回调函数中return的值)赋给result
- 判断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
具体看
到这里整个Promise基本就已经实现了,剩下一些自带函数我们留着下回实现