这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
- 理解promise的基本使用,和返回新promise对象规则
- 理解怎么去手写一个promise
本篇要聚焦怎么手写Promise
了,如果没有读过从0到1,理解异步编程方案Promise(0)
这篇文章的同学,请先看这篇文章,因为接下来手写Promise
将要运用到那些知识。
手写Promise
之前,先简单的看一下Promise
怎么使用的,不过我相信看这篇文章的同学,对使用Promise
的应该是很清楚了的,如果不清楚请戳这里
1. Promise简单介绍
1.1 基本使用
【Promise基本理解图:】
这里可以知道,Promise有3个状态
- peeding,待定
- resolved(fulfilled),已兑现
- rejected,已拒绝
使用起来如下:
let key = true;
const testpromise = new Promise((resolve,reject)=>{
// 模拟异步
setTimeout(()=>{
if(key){
resolve("成功了执行")
}else{
reject("失败了执行")
}
},1000)
})
const result = testpromise.then(res=>{
console.log(res)
}).catch(error=>{
console.log(error)
})
console.log( result )
当key = true
,很显然是进入兑现流程,然后result
接受到的是一个Promise
对象,展开可以看到PromiseState
已经是"fulfilled"
状态
若当key = false
,很显然进入到了拒绝流程,然后result
接受的是还是Promise
对象,展开可以看到PromiseState
已经是rejected
1.2 注意点
虽然Promise
是异步解决方案,但是他其中的构造函数和Promise
的执行器是同步的,而且大家也可能忽略了一个地方,就是then方法其实可以有两个回调函数作为参数,第一个为兑现回调一个为拒绝回调
let key = false;
const testpromise = new Promise((resolve,reject)=>{
// 模拟异步
setTimeout(()=>{
if(key){
resolve("成功了执行")
}else{
reject("失败了执行")
}
},1000)
})
const result = testpromise.then(res=>{
console.log(res)
},(res)=>{
console.log(res)
})
1.3 其他Api
Promise.resolve()
new Promise((resolve,_)=>{
resolve(1)
})
// 等效于
Promise.resolve(1)
Promise.reject()
:
new Promise((_,reject)=>{
reject(1)
})
// 等效于
Promise.reject(1)
Promise.all
:创建的promise会在一组promise全部解决后再解决Promise.race()
:返回一个包装promise,是一组集合中最先解决或拒绝的promise的镜像
2. 自定义实现Promise
来了,重点来了,要开始手动实现一个Promise
了
2.1 首先构造一个CustomPromise
类
从Promise
的使用上来看,需要以下这几个东西:
- 一个实例化参数
notify
,一个公共属性state用于保存反应当前promise实例的状态,一共
peeding、
fulfilled、
rejected`这三个状态 - 要有一个
then
公共方法来处理行为和一个catch
公共方法来捕捉错误 - 一个
resolve
,一个reject
静态方法(all和race这里就不实现了,只实现核心部分,有兴趣可以自己实现)
综上有下面初步代码
class CustomPromise{
constructor(notify) {
// 初始化状态为 peeding
this.state = CustomPromise.state[0];
}
then(onResovled,onRejected){
console.log('then')
}
catch(onRejected){
console.log('catch')
}
static resolve (){
console.log('resolve')
}
static reject(){
console.log('reject')
}
}
CustomPromise.state = ['peeding','fulfilled','rejected'];
// 基本测试用例 这部分可以忽略
const testCustomPromise = new CustomPromise(( resolve, reject)=>{
setTimeout(()=>{
resolve('处理对象')
},1000)
})
testCustomPromise.then((res)=>{
console.log(res)
})
2.2 打造CustomPromise的执行器和then方法
我们在使用Promise 一般都是
const testpromise = new Promise((resolve,reject)=>{
// 模拟异步
setTimeout(()=>{
resolve("成功了执行")
},1000)
})
const result = testpromise.then(res=>{
console.log(res)
})
其实这里就是一个发布订阅模式的一个变种了,then就是添加订阅者的行为,如果是作为第一个参数就是订阅fulfilled
这个消息,如果是第二个参数订阅rejected
这个消息代入这个思想,完善代码如下
class CustomPromise{
constructor(notify) {
// 初始化状态为 peeding
this.state = CustomPromise.state[0];
this.actionfnList = new Map([['fulfilled',()=>{}],['rejected',()=>{}]]);
}
then(onResovled,onRejected){
console.log('then')
this.actionfnList.set('fulfilled',onResovled)
this.actionfnList.set('rejected',onResovled)
}
catch(onRejected){
console.log('catch')
}
static resolve (){
console.log('resolve')
}
static reject(){
console.log('reject')
}
}
写到这里,运行测试用例,发现打印了then
,说明行为我们已经订阅。那么怎么触发行为呢?这里就和发布订阅模式有一点点区别了,在发布订阅模式,状态的通知是显示的,使用的是notify([状态])
,然后触发订阅该状态的行为。只是promise的状态通知只是稍微有一点点隐藏,不是那么明显罢了。既然不明显,那么是哪里改变了状态呢通知行为呢?
那就是在new CustomPromise()
作为参数的那个函数改变的消息状态,这里称notify
,这个notify
又接受2个参数,一个是resolve
函数,改变消息为fulfilled
,一个是reject
函数改变消息为rejected
,而且这两个函数不仅仅只有改变状态触发订阅动作,因为订阅动作也是能入参的,而这个参数就是这两个函数的入参,因此代码可以进一步完善。
class CustomPromise{
constructor(notify) {
// 初始化状态为 peeding
this.state = CustomPromise.state[0];
this.actionfnList = new Map([['fulfilled',()=>{}],['rejected',()=>{}]]);
const resolveRun = (res)=>{
if(this.state !== CustomPromise.state[0]){
return
}
this.state = CustomPromise.state[1]
const action = this.actionfnList.get(this.state)
// 因为promise是微任务这里就要使用js的微任务队里
queueMicrotask(()=>{
action(res)
})
}
const rejectRun = (error)=>{
if(this.state !== CustomPromise.state[0]){
return
}
this.state = CustomPromise.state[2]
this.state = CustomPromise.state[2]
const action = this.actionfnList.get(this.state)
// 因为promise是微任务这里就要使用js的微任务队里
queueMicrotask(()=>{
action(error)
})
}
// 状态消息改变通知
notify(resolveRun,rejectRun)
}
then(onResovled,onRejected){
this.actionfnList.set('fulfilled',onResovled)
this.actionfnList.set('rejected', onResovled)
}
catch(onRejected){
console.log('catch')
}
static resolve (){
console.log('resolve')
}
static reject(){
console.log('reject')
}
}
使用测试用例,嗯好像核心功能都实现了哦! 如果这样想,那就真是太年轻了
2.3 处理then()返回一个新的promise
为什么要实现这一步,因为 Promise.prototype.then
和 Promise.prototype.catch
方法返回的是 一个新的promise
对象。那这个新的promise对象怎么来呢?遵循下面3个规则:
- 如果then抛出异常,return的promise就会失败
value
就是error
- 如果then的回调函数返回不是
promise
,return的promise就会成功,value
就是返回值 - 如果回调函数返回的是一个
promise
,return的promise
的结果就是这个promise的结果
这是怎么意思呢?举例例子
const testpromise = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("testpromise")
},1000)
})
const onetest = new Promise(( resolve,reject)=>{
setTimeout(()=>{
resolve('测试用例,成功1')
},)
})
const twotest = new Promise(( resolve,reject)=>{
setTimeout(()=>{
reject('测试用例,测试2')
},)
})
const result0 = testpromise.then((res)=>{return res})
const result1 = testpromise.then(()=>onetest)
const result2 = testpromise.then(()=>twotest)
const result3 = testpromise.then(()=>new Error('报错了'))
console.log(result0) // [[PromiseResult]]: "testpromise" 对应上述第二条规则
console.log(result1) // [[PromiseResult]]: "测试用例,成功1" 对应上述第三条规则
console.log(result2) // [[PromiseResult]]: "测试用例,测试2" 对应上述第三条规则
console.log(result3) // [[PromiseResult]]: Error: 报错了 对应上述第一条规则
综上,then的实现就不是之前简简单单,做一个收集订阅者动作的操作了,而是要根据不同的this.state
的状态,除了做收集订阅者动作,还要根据动作的是不是也是一个promise和动作的返回值来返回一个新的promise
对象,因此对上面代码做如下改造,一份核心的手写promise完整代码就产生了
class CustomPromise{
constructor(notify) {
this.state = CustomPromise.state[0];
this.actionfnList = new Map([['fulfilled',()=>{}],['rejected',()=>{}]]);
// 因为then 方法要返回对当前promise resolve或reject的值进行promise包装,因此需要用一个属性来保存该值
this.data = null
const resolveRun = (res)=>{
if(this.state !== CustomPromise.state[0]){
return
}
this.state = CustomPromise.state[1]
this.data = res;
const action = this.actionfnList.get(this.state)
// 因为promise是微任务这里就要使用js的微任务队里
queueMicrotask(()=>{
action(res)
})
}
const rejectRun = (error)=>{
if(this.state !== CustomPromise.state[0]){
return
}
this.state = CustomPromise.state[2]
this.data = error
const action = this.actionfnList.get(this.state)
// 因为promise是微任务这里就要使用js的微任务队里
queueMicrotask(()=>{
action(error)
})
}
// 状态消息改变通知
notify(resolveRun,rejectRun)
}
then(onResovled,onRejected){
// 1. 首先对onResovled/onRejected进行一个类型判断如果是函数就不变如果不是,就用为(res)=>res 用当前promise的值进行返回
onResovled = typeof onResovled === 'function'? onResovled: res=>res
onRejected = typeof onRejected === 'function'? onRejected: error=>error
// 2. 返回一个promise
return new CustomPromise( (resolve,reject)=>{
// 3. 根据上面提到的三条规则写一个处理函数,来确定我应该返回一个什么样的promise
const handle = (subscriberAction,data)=>{
try{
const result = subscriberAction(data);
// 3.1 判断result是CustomPromise 如果是就 返回这个promise的结果
if( result instanceof CustomPromise){
result.then(resolve,reject)
}else{
// 如果不是就用这个返回值作为这个新promise的值
resolve( result )
}
}catch(error){
reject( error)
}
}
switch(this.state){
case CustomPromise.state[0]:
// 如果 peeding 做收集订阅者动作
this.actionfnList.set('fulfilled',(res)=>{ handle(onResovled,res) })
this.actionfnList.set('rejected',(error)=>{ handle(onRejected,error) })
break;
case CustomPromise.state[1]:
// 如果是 fulfilled 处理onResovled
queueMicrotask(()=>{
handle(onResovled,this.data)
})
break;
case CustomPromise.state[2]:
// 如果是 reject onRejected
queueMicrotask(()=>{
handle(onRejected,this.data)
})
break;
default:
// 默认做 做收集订阅者动作
this.actionfnList.set('fulfilled',()=>{ handle(onResovled) })
this.actionfnList.set('rejected',()=>{ handle(onRejected) })
break;
}
})
}
catch(onRejected){
console.log('catch')
return this.then(undefined,onRejected)
}
// 顺便也把静态方法也做了
static resolve (value){
return new CustomPromise((resolve,reject)=>{
if( value instanceof CustomPromise){
value.then(resolve,reject)
}else{
resolve(value)
}
})
}
static reject(error){
return new CustomPromise((resolve,reject)=>{
reject(error)
})
}
}
CustomPromise.state = ['peeding','fulfilled','rejected'];
可能还有瑕疵!!! 就先这样吧,简单能理解为主