关于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)
})
}
}
}