promise源码从零开始编写记录之二(编写then与异步处理)

关于Then的处理,还需要从resolve说起

从使用方式开始 

let promise = new Promise((resolve, rejected) => {//我已经习惯了写rejected,个人使用风格
    // todo....
     resolve('data')  
})

promise.then(data => {
  console.log(data)  //成功
}, error => {
  console.log(error) //失败
})

那么,我们再来看看PromiseA+的规范,是否有关于then的记录

其实阔以看到,形式就是:

   传入两个参数,onFulfilled,onRejected,

  根据promiseA+规范,

1  onFulfilled 和onRejected都是一个可选参数

   1.1 如果onFulfilled不是一个参数,那么他就必须被忽略掉,也就是我们不会去处理它

   1.2 如果onRejected不是一个参数,那么他就必须被忽略掉,也就是我们不会去处理它

但我们现在不去处理这些细节的东西,接着上次的的代码继续添加

 思考,什么情况才会进入then?

    只有两种情况,resolve() 和 rejected()

所以我们可以写下then函数,框架先搭好,一步一步往里面加内容

class Promise{

  constructor(executor){
    //...logic
  }

  then(onFulfilled, onRejected){
         
  }
 
}

第一,then必须判断状态,promise对于函数状态是不可逆的

new Promise(resolve => {resolve()})   --> 此时状态已经更改为RESOLVE

那么想想如果then不判断状态的情况,会是什么样子的呢,根据刚刚提到的规范,去编写,不是一个参数就忽略它?

then(onFulfilled, onRejected){
  //因为这些函数是可选的,所以如果传入,按一般思维,有就执行没有就不执行
    onFulfilled && onFulfilled()    //又走成功
    onRejected && onRejected()      //又走失败
}

那岂不是resolve以后,走了reject的方法,这样会导致整个程序完全错误,必然逻辑上是错误的,所以如何判断只执行一个函数呢?

把promiseA+搬出来读一读:
     

2 如果onFulfilled是一个函数
  2.1 onFulfilled必须在promise是fulfilled以后被调用,并且把promise的值作为其第一个参数
    其实就是 resolve(data),把data作为onFulfilled的第一个参数
  2.2 onFulfilled不能在promise的fulfilled状态之前调用
  2.3 onFulfilled不能调用多次

 先不考虑两个函数都被运行的问题,先来实现2.1吧

then(onFulfilled, onRejected){
    //resolve的值我们在resolve(data)的时候传入进来保存到了this.value了
    //所以我们需要直接拿到这段数据作为函数传参
    onFulfilled && onFulfilled(this.value)
    onRejected && onRejected(this.reasion)
}

然后再看看2.2,不能在resolve()之前被调用,这句话的意思,就是状态没发生改变前不允许发生函数,那么我在第一篇文章写了,定义了三个状态,分别是RESOLVE,REJECTED,PENDING

规范规定,状态一旦更改,将会是不可逆的状态,也就是不再允许更改了,那就阔以进行愉快的判断拦截啦

 

        首先在构造器里面进行判断,因为状态的更替是由promise的executor函数改变的,所以可以很有效的进行更改

let resolve = value => {
    if(this.status === PENDING){        //如果状态从没改变过,进行更改
        this.status = RESOLVE
        this.value = value
    }
}

let rejected = reason => {              //如果状态从没发生改变过,进行更改
    if(this.status === PENDING){
        this.status = REJECTED
        this.reason = reason
    }
}

executor(resolve, rejected)

 这样就能有效阻止多次改变状态,虽然这样阻止了多次改变状态,但如果是对于先执行then,再执行resolve这种情况,应该如何去应   对?

 其实可以用异步来模拟场景

//普通场景下的promise
let promise = new Promise(resolve => {resolve()})
promise.then(res => {console.log('Is ok!')})  //此时是可以运行的

//异步场景下的promise
let promise = new Promise((resolve, rejected) => {
 setTimeOut(() => {
    resolve('data')
 }, 1000)
})

promise.then(res => {
    console.log(res) //undefined
})

      1.普通场景下,resolve()立即执行,给promise赋值,然后调用then方法,我们是可以从promise内部的this.value取到值的

      2.但在异步环境下,内部是先执行setTimeOut,然后发现是一个异步模型,就会放入另一个调用栈等待主线程执行完毕

      所以在then里面,this.value是读取不到的

 

 需要进行一波发布订阅操作,先把函数存起来,然后在resolve的时候执行,可以理解为,存起来的操作是订阅报纸,跟报社说了,但没有给我执行,resolve以后执行就是发布报纸,直接送到你家

 

在构造器内部接着定义两组Array

this.onResolveCallbacks = [] //成功的回调数组
this.onRejectedCallbacks = []   //失败回调数组

搞定以后,改造then函数,一旦发现有调用then,但状态是PENDING,说明resolve没有执行,我们必须来一波订阅

if(this.status = PENDING){
        //如果是异步,就先订阅好
        this.onResolveCallbacks.push(onfullfilled(this.value))
     this.onRejectedCallbacks.push(onrejected(this.reason))
}

不过这种写法真的是缺乏灵活性,如果想做一点其他的操作就不行了,所以还需要用到AOP思想,包装成一个函数,存入函数之前想写啥就写啥,每次调用函数前,都会进行一波处理,这样灵活性提高了

if(this.status = PENDING){
        //如果是异步,就先订阅好
        this.onResolveCallbacks.push(() => {    //重写push方法的时候
        //todo ...这是一个切片写法
        onfullfilled(this.value)
      })
     this.onRejectedCallbacks.push(() => {
     //todo...
     onrejected(this.reason)
  })
}

 

既然订阅了,就进行最后一步,发布模式,很容易想到,既然是异步,我们就在resolve/rejected后进行发布,就实现了

resolve/rejected内部循环取出调用

      this.onResolveCallbacks.forEach(fn => fn()) / this.onRejectedCallbacks.forEach(fn => fn())

 

异步的问题也解决了,最终代码

const PENDING = 'PENDING'
const RESOLVE = 'RESOLVE'
const REJECTED = 'REJECTED'

export default class Promise{
    constructor(executor){
        this.status = PENDING
        this.reason = undefined //失败的原因
        this.value = undefined //成功的值

        this.onResolveCallbacks = [] //成功的回调数组
        this.onRejectedCallbacks = []   //失败回调数组

        //成功函数
        let resolve = value => {
            if(this.status === PENDING){  //防止调用rejected,又调用resolve
                this.value = value
                this.status = RESOLVE
                this.onResolveCallbacks.forEach(fn => fn())
            }
        }
        
        //失败函数
        let rejected = reason => {
            if(this.status === PENDING){ //同理
                this.reason = reason
                this.status = REJECTED
                this.onRejectedCallbacks.forEach(fn => fn())
            }
        }

        try{
            executor(resolve, rejected)  //默认就立刻执行
        }catch(e){
            rejected(e)  //如果执行时发生错误,等价于调用失败方法
        }
    }

    then(onfullfilled, onrejected){     //then目前有两个参数
        if(this.status === RESOLVE){
            onfullfilled(this.value)
        }
        if(this.status === REJECTED){
            onrejected(this.reason)
        }
        if(this.status = PENDING){
            //如果是异步,就先订阅好
            this.onResolveCallbacks.push(() => {
                //todo ...这是一个切片写法
                onfullfilled(this.value)
            })
            this.onRejectedCallbacks.push(() => {
                //todo...
                onrejected(this.reason)
            })
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值