使用class手搓Promise,三步一回头
理解本文的实现逻辑需要掌握Promise及其使用方法。
搭建初始结构
这里我们采用类搭建Promise的初始结构。我们创建Promise对象时,一般写法let p = new Promise((resolve,reject)=>{resolve(222)})
。我们输出p看一下。
我们首先在构造函数中声明并执行创建对象时的(resolve,reject)=>{resolve(222)}这个回调函数。
class CustomPromise {constructor(initFunc) {initFunc()}}
这个时候如果我们创建CustomPromise的实例对象会报错。因为resolve,reject
两个入参我们没有声明,在执行到resolve(222)
时报错。下面进行声明:
class CustomPromise {constructor(initFunc) {initFunc(this.resolve, this.reject)}resolve(value) {}reject(value) { }}
这个时候我们执行let cp = new CustomPromise((resolve, reject) => { resolve(222) })
这段代码看一下
已经不报错了,非常的棒。但是是没有任何属性的空对象。我们把Promise的三个状态:等待状态pending、成功状态fulfilled、失败状态rejected和值声明出来。
class CustomPromise {constructor(initFunc) {this.status = 'pending'this.result = undefinedinitFunc(this.resolve, this.reject)}resolve(value) {}reject(value) {}
}
好,基本架构搭建完成。
搭建resolve和reject方法
PromiseA+规范中说明,Promise状态只能通过pending转为fulfilled或rejected。我们需要在resolve和reject函数中增加此逻辑。这两个函数主要功能是改变Promise的状态和设置Promise的值。我们来实现:
class CustomPromise {constructor(initFunc) {this.status = 'pending'this.result = undefinedinitFunc(this.resolve.bind(this), this.reject.bind(this))}resolve(value) {if (this.status !== 'pending') returnthis.status = 'fulfilled'this.result = value}reject(value) {if (this.status !== 'pending') returnthis.status = 'rejected'this.result = value}
}
因为initFunc函数内参数是回调函数,所以resolve和reject函数只能手动声明this,我们通过bind手动绑定。这里不能通过call进行绑定,因为resolve函数时用户调用的。
这里在初始化对象时,执行的回调函数(CustomPromise中的initFunc执行时要处理异常情况,所以需要用try...catch
处理)
至此,初始结构丰满完成,已经初具人形了。
搭建then方法
我们平时使用Promise时:
let p = new Promise((resolve, reject) => { resolve(222) })let pp = p.then(res => {console.log(res, 'pp-res')},err => {console.log(err, 'pp-err')})
大概像这样,会输出:(这段Promise的逻辑输出后面文章也会输出,代码就不放了。)
所以我们看到Promise的then函数会实现的基础功能为:
- 接收两个回调函数
- 根据p不同的状态调用不同的函数
class CustomPromise {constructor(initFunc) {this.status = 'pending'this.result = undefinedtry {initFunc(this.resolve.bind(this), this.reject.bind(this))} catch (error) {this.reject(error)}}resolve(value) {if (this.status !== 'pending') returnthis.status = 'fulfilled'this.result = value}reject(value) {if (this.status !== 'pending') returnthis.status = 'rejected'this.result = value}then(onFulfilled, onRejected) {if (this.status === 'fulfilled') {onFulfilled(this.result)}if (this.status === 'rejected') {onRejected(this.result)}}
}
这样就实现了then的基本逻辑。
兼容异步任务
上面我们封装CustomPromise都是基于CustomPromise同步创建,这样initFunc函数(初始回调函数)>resolve函数(改变状态和值)>then函数可以同步执行,如果异步创建呢?我们执行这段程序。
let cp = new CustomPromise((resolve, reject) => {setTimeout(() => {resolve(222)}, 1000);
})console.log(cp)
cp.then(res => {console.log(res, '1')
})
我们看到输出的cp的状态为pending,then函数的回调函数没有执行。
为啥子呢?一开始创建cp时,status初始状态就是pending,reault值为undefined。这个时候resolve在宏任务中,被排到了下一次事件循环。接着就是执行同步任务,输出了cp,即为初始状态。接着是执行then函数,此时status的状态还是pending,在我们目前搭建的CustomPromise中,并没有对此状态的处理。
因此,增加此逻辑处理,把此状态下then的回调函数保存起来,等待resolve或reject函数内容执行完成后再执行。
class CustomPromise {constructor(initFunc) {this.status = 'pending'this.result = undefinedthis.onFulfilledFunc = undefinedthis.onRejectedFunc = undefinedtry {initFunc(this.resolve.bind(this), this.reject.bind(this))} catch (error) {this.reject(error)}}resolve(value) {if (this.status !== 'pending') returnthis.status = 'fulfilled'this.result = valuethis.onFulfilledFunc && this.onFulfilledFunc(this.result)}reject(value) {if (this.status !== 'pending') returnthis.status = 'rejected'this.result = valuethis.onRejectedFunc && this.onRejectedFunc(this.result)}then(onFulfilled, onRejected) {if (this.status === 'fulfilled') {onFulfilled(this.result)}if (this.status === 'rejected') {onRejected(this.result)}if (this.status === 'pending') {this.onFulfilledFunc = onFulfilledthis.onRejectedFunc = onRejected}}
}
这里我们看到上面程序中的输出也在最后输出了出来。所以如果onFulfilledFunc或onRejectedFunc有值的话,就说明创建CustomPromise时,是异步创建。此逻辑的处理相当于延后then函数中回调函数的执行。
另外,PromiseA+规范中说明,
- then函数内参数必须为函数类型
- then函数支持多次调用
- then函数应该返回Promise
then函数内参数必须为函数类型的要求我们在then函数中判断一下即可。then函数支持多次调用这个要求我们看一下:
let cp = new CustomPromise((resolve, reject) => {// setTimeout(() => {resolve(222)// }, 1000);
})
cp.then(res => {console.log(res, 'cp-res')
})
cp.then(res => {console.log(res, '1')
})
同步创建对象,执行时没啥问题。如果把setTimeout函数注释取消呢?
Promise那段逻辑输出的pp-res在前,1这个在后了,顺序正确。但是cp-res的输出内容消失了!
(1)创建CustomPromise,状态为pending、值为undefined,
(2)执行第一个then函数,判断状态为pending,保存回调函数至onFulfilledFunc
(3)执行第二个then函数,判断状态为pending,替换回调函数onFulfilledFunc!
(4)setTimeout函数到主线程执行,执行完成后执行替换后的onFulfilledFunc函数。
所以,为了支持多次调用,我们需要把保存的变量修改为数组
class CustomPromise {constructor(initFunc) {this.status = 'pending'this.result = undefinedthis.onFulfilledFuncList = []this.onRejectedFuncList = []try {initFunc(this.resolve.bind(this), this.reject.bind(this))} catch (error) {this.reject(error)}}resolve(value) {if (this.status !== 'pending') returnthis.status = 'fulfilled'this.result = valueif (this.onFulfilledFuncList.length > 0) {this.onFulfilledFuncList.forEach(func => func(this.result))}}reject(value) {if (this.status !== 'pending') returnthis.status = 'rejected'this.result = valueif (this.onRejectedFuncList.length > 0) {this.onRejectedFuncList.forEach(func => func(this.result))}}then(onFulfilled, onRejected) {// 判断入参是否为函数let realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => valuelet realOnRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }if (this.status === 'fulfilled') {realOnFulfilled(this.result)}if (this.status === 'rejected') {realOnRejected(this.result)}// 支持多次调用then函数if (this.status === 'pending') {this.onFulfilledFuncList.push(onFulfilled)this.onRejectedFuncList.push(onRejected)}}
}
如此,这般。
then函数返回Promise
同步情况
因为then函数执行时,回调函数可能返回
- CustomPromise
- 原始数据类型或引用数据类型
- 异常
- 无回调函数
我们依次看怎么处理
- CustomPromise
直接返回即可 - 原始数据类型或引用数据类型
通过CustomPromise返回,值为返回值,状态为fulfilled(resolve) - 异常
通过CustomPromise返回,值为异常值,状态为rejected(reject) - 无回调函数
通过CustomPromise返回,状态和值继承调用then函数的CustomPromise对象(相当于没走then函数,直接返回)
then(onFulfilled, onRejected) {// 判断入参是否为函数let realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => valuelet realOnRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }return new CustomPromise((resolve, reject) => {if (this.status === 'fulfilled') {try {// 回调函数的值let value = realOnFulfilled(this.result)if (value instanceof CustomPromise) {// 执行后调用回调函数,到最外层返回value.then(res => resolve(res), err => reject(err))} else {resolve(value)}} catch (error) {// 处理异常reject(error)}}if (this.status === 'rejected') {try {// 回调函数的值let value = realOnRejected(this.result)if (value instanceof CustomPromise) {// 执行后调用回调函数,到最外层返回value.then(res => resolve(res), err => reject(err))} else {resolve(value)}} catch (error) {// 处理异常reject(error)}}// 支持多次调用then函数if (this.status === 'pending') {this.onFulfilledFuncList.push(onFulfilled)this.onRejectedFuncList.push(onRejected)}})}
执行此段逻辑
let cp = new CustomPromise((resolve, reject) => {resolve(222)
})
let cp2 = cp.then(res => {console.log(res, 'cp-res')
})
console.log(cp2, 'cp2')
(1)这里then的回调函数没有返回,其实是按undefined处理(原始数据类型)
(2)如果then返回CustomPromise类型,执行value.then(res => resolve(res), err => reject(err))
此时value即为CustomPromise类型。目前处理是执行value对象的then方法,通过第五行统一封装返回。
ps.也可以每种情况单独返回,不通过第五行统一返回。那么第一种情况"then的回调函数没有返回"这么写return new Promise(resolve=>resolve(value))
,如果是CustomPromise类型,直接返回return value
。异常捕获这么写return new Promise((resolve,reject)=>reject(value))
(3)异常通过trycatch捕获
(4)无回调函数
此时判断then函数是否为函数的第三、四行为空时的默认函数,就是返回的调用then函数的CustomPromise对象的值。
异步情况
异步和同步的逻辑相同,处理方式略有不同
then(onFulfilled, onRejected) {// 判断入参是否为函数let realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => valuelet realOnRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }return new CustomPromise((resolve, reject) => {if (this.status === 'fulfilled') {try {let value = realOnFulfilled(this.result)if (value instanceof CustomPromise) {// 执行后调用回调函数,到最外层返回value.then(res => resolve(res), err => reject(err))} else {resolve(value)}} catch (error) {// 处理异常reject(error)}}if (this.status === 'rejected') {try {let value = realOnRejected(this.result)if (value instanceof CustomPromise) {// 执行后调用回调函数,到最外层返回value.then(res => resolve(res), err => reject(err))} else {resolve(value)}} catch (error) {// 处理异常reject(error)}}// 支持多次调用then函数if (this.status === 'pending') {this.onFulfilledFuncList.push((result) => {try {let value = realOnFulfilled(result)if (value instanceof CustomPromise) {// 执行后调用回调函数,到最外层返回value.then(res => resolve(res), err => reject(err))} else {resolve(value)}} catch (error) {// 处理异常reject(error)}})this.onRejectedFuncList.push((result) => {try {let value = realOnRejected(result)if (value instanceof CustomPromise) {// 执行后调用回调函数,到最外层返回value.then(res => resolve(res), err => reject(err))} else {resolve(value)}} catch (error) {// 处理异常reject(error)}})}})}
这里把then函数的回调函数封装起来,执行后进行返回。result作为入参,是在resolve或reject函数执行时,传入的参数。
方法封装
现在我们把返回CustomPromise这段公共代码封装起来
then(onFulfilled, onRejected) {// 判断入参是否为函数let realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => valuelet realOnRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }return new CustomPromise((resolve, reject) => {if (this.status === 'fulfilled') {try {let value = realOnFulfilled(this.result)this.handleThenReturnPromise(value, resolve, reject)} catch (error) {// 处理异常reject(error)}}if (this.status === 'rejected') {try {let value = realOnRejected(this.result)this.handleThenReturnPromise(value, resolve, reject)} catch (error) {// 处理异常reject(error)}}// 支持多次调用then函数if (this.status === 'pending') {this.onFulfilledFuncList.push((result) => {try {let value = realOnFulfilled(result)this.handleThenReturnPromise(value, resolve, reject)} catch (error) {// 处理异常reject(error)}})this.onRejectedFuncList.push((result) => {try {let value = realOnRejected(result)this.handleThenReturnPromise(value, resolve, reject)} catch (error) {// 处理异常reject(error)}})}})
}handleThenReturnPromise(value, resolve, reject) {if (value instanceof CustomPromise) {// 执行后调用回调函数,到最外层返回value.then(res => resolve(res), err => reject(err))} else {resolve(value)}
}
网上有很多方法直接封装成resolvePromise方法,处理了一些公共业务,我们只针对CustomPromise这一种情况,再加上对then函数返回的是嵌套CustomPromise的递归逻辑处理。
handleThenReturnPromise(value, resolve, reject) {if (value instanceof CustomPromise) {// 执行后调用回调函数,到最外层返回value.then(res => this.handleThenReturnPromise(res, resolve, reject), err => reject(err))} else {resolve(value)}
}
微任务
PromiseA+规范规定onFulfilled和onRejected应该是微任务。Promise中then方法的回调函数输出逻辑也是如此:
let p = new Promise((resolve, reject) => { resolve(222) })
let pp = p.then(res => {console.log(res, 'res')
})
console.log(pp, 'pp')
这段逻辑输出如下:
先执行同步任务(第五行),再执行微任务(then的回调函数)
目前CustomPromise中then的回调函数时同步任务,我们看下执行
let cp = new CustomPromise((resolve, reject) => {resolve(123)console.log("after")
})
let cp2 = cp.then((res) => {console.log(res, 'res')},(error) => {console.log(error, 'error')}
)
console.log('end')
执行结果:
所以我们使用queueMicrotask把then的回调函数置为微任务。
class CustomPromise {constructor(initFunc) {this.status = 'pending'this.result = undefinedthis.onFulfilledFuncList = []this.onRejectedFuncList = []try {initFunc(this.resolve.bind(this), this.reject.bind(this))} catch (error) {this.reject(error)}}resolve(value) {if (this.status !== 'pending') returnthis.status = 'fulfilled'this.result = valueif (this.onFulfilledFuncList.length > 0) {queueMicrotask(() => {this.onFulfilledFuncList.forEach(func => func(this.result))})}}reject(value) {if (this.status !== 'pending') returnthis.status = 'rejected'this.result = valueif (this.onRejectedFuncList.length > 0) {queueMicrotask(() => {this.onRejectedFuncList.forEach(func => func(this.result))})}}then(onFulfilled, onRejected) {// 判断入参是否为函数let realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => valuelet realOnRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }return new CustomPromise((resolve, reject) => {if (this.status === 'fulfilled') {queueMicrotask(() => {try {let value = realOnFulfilled(this.result)this.handleThenReturnPromise(value, resolve, reject)} catch (error) {// 处理异常reject(error)}})}if (this.status === 'rejected') {queueMicrotask(() => {try {let value = realOnRejected(this.result)this.handleThenReturnPromise(value, resolve, reject)} catch (error) {// 处理异常reject(error)}})}// 支持多次调用then函数if (this.status === 'pending') {this.onFulfilledFuncList.push((result) => {try {let value = realOnFulfilled(result)this.handleThenReturnPromise(value, resolve, reject)} catch (error) {// 处理异常reject(error)}})this.onRejectedFuncList.push((result) => {try {let value = realOnRejected(result)this.handleThenReturnPromise(value, resolve, reject)} catch (error) {// 处理异常reject(error)}})}})}handleThenReturnPromise(value, resolve, reject) {if (value instanceof CustomPromise) {// 执行后调用回调函数,到最外层返回value.then(res => this.handleThenReturnPromise(res, resolve, reject), err => reject(err))} else {resolve(value)}}catch(onRejected) {return this.then(null, onRejected)}
}
在then函数的回调函数执行时添加到微任务队列。
搭建catch方法
我们可以通过调用then函数,把catch的回调函数作为第二个参数传入。
catch(onRejected) {return this.then(null, onRejected)
}
测试一下:
let cp = new CustomPromise((resolve, reject) => {reject(123)
})
cp.catch(err => {console.log(err, 'err')
})
(1)创建CustomPromise对象后,cp为值为123,状态为rejected的对象;
(2)执行catch方法,走then方法中status==="rejected"
的逻辑返回resolve(123)
。
let cp = new CustomPromise((resolve, reject) => {reject(123)
})
cp.then(res => {console.log(res, 'res')}).catch(err => {console.log(err, 'err')})
这段代码相较于上段代码增加了一段then。两段代码的执行结果相同。
(1)创建CustomPromise对象后,cp为值为123,状态为rejected的对象;
(2)执行then函数,判断状态status==="rejected"
,但是入参中第二个参数onRejected参数为空,赋值为默认方法error => { throw error }
,抛出异常后被try…catch捕获,返回CustomPromise对象,状态为reject,值为123。
(3)执行catch方法,走then方法中status==="rejected"
的逻辑返回resolve(123)
。
搭建CustomPromise.resolve()和CustomPromise.reject()方法
类似Promise.resolve()和Promise.reject()方法,可以直接通过类名调用,返回Promise。
static resolve(value) {if (value instanceof CustomPromise) {return value} else {return new CustomPromise(resolve => resolve(value))}
}static reject(value) {return new CustomPromise((resolve, reject) => reject(value))
}
声明静态方法,可以直接调用。
搭建finally方法
finally方法它是保证在Promise调用链的哪一节都会执行并且向后传递调用它的Promise对象。
finally(cb) {// 返回CustomPromisereturn this.then(// 如果调用finally方法的CustomPromise状态为fulfilled,调用此方法(value) => {// 首先保证finally方法中的回调函数执行完成// 返回调用finally方法的CustomPromise的值给后续链使用return CustomPromise.resolve(cb()).then(() => value)},// 如果调用finally方法的CustomPromise状态为rejected,调用此方法(value) => {return CustomPromise.resolve(cb()).then(() => { throw value })})
}
最后还有一些方法像all、race等方法就不多介绍了,如果感兴趣可以点这里看一下,非常详细。
参考:https://juejin.cn/post/7480797795785687040#heading-21
https://zhuanlan.zhihu.com/p/183801144
https://zhuanlan.zhihu.com/p/451254515