从0到1,理解异步编程方案Promise(1)

339 阅读7分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

  1. 理解promise的基本使用,和返回新promise对象规则
  2. 理解怎么去手写一个promise

本篇要聚焦怎么手写Promise了,如果没有读过从0到1,理解异步编程方案Promise(0)这篇文章的同学,请先看这篇文章,因为接下来手写Promise将要运用到那些知识。

手写Promise之前,先简单的看一下Promise怎么使用的,不过我相信看这篇文章的同学,对使用Promise的应该是很清楚了的,如果不清楚请戳这里

1. Promise简单介绍

1.1 基本使用

【Promise基本理解图:】

Promise基本理解图.png

这里可以知道,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

  1. Promise.resolve()
new Promise((resolve,_)=>{
   resolve(1)
})
// 等效于
Promise.resolve(1)
  1. Promise.reject():
new Promise((_,reject)=>{
   reject(1)
})
// 等效于
Promise.reject(1)
  1. Promise.all:创建的promise会在一组promise全部解决后再解决
  2. Promise.race():返回一个包装promise,是一组集合中最先解决或拒绝的promise的镜像

2. 自定义实现Promise

来了,重点来了,要开始手动实现一个Promise

2.1 首先构造一个CustomPromise

Promise的使用上来看,需要以下这几个东西:

  1. 一个实例化参数notify,一个公共属性state 用于保存反应当前promise实例的状态,一共peedingfulfilledrejected`这三个状态
  2. 要有一个then公共方法来处理行为和一个catch公共方法来捕捉错误
  3. 一个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.thenPromise.prototype.catch 方法返回的是 一个新的promise对象。那这个新的promise对象怎么来呢?遵循下面3个规则:

  1. 如果then抛出异常,return的promise就会失败value就是error
  2. 如果then的回调函数返回不是promise,return的promise就会成功,value就是返回值
  3. 如果回调函数返回的是一个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'];

可能还有瑕疵!!! 就先这样吧,简单能理解为主

参考资料

  1. Promise