掰开揉碎系列——手写Promise

315 阅读7分钟

前言

笔者最近在整理Promise的内容,今天给大家分享如何从0手搓出一个简单的Promise

Promise结构搭建

第一步实现promise的第一个回调被立马调用
第二步设置TTPromise类中的reslove reject函数,并将其传出
第三步保存reslove reject的参数,方便后续给then过去
第四步设置状态,保证只有在pending状态下才可以调用reslove/reject

// 第四步设置三种状态状态
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'

// 
class PHPromise {
    constructor(excutor) {
        this.status = PROMISE_STATUS_PENDING
        // 第三步.保存reslove reject 要传递给 res err 的参数
        this.value = undefined
        this.reason = undefined
        // 	第二步设置reslove函数
        const reslove = (value) => {
            // 第四步状态设计,只有pending期间才可以调用
            if(this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_FULFILLED
                this.value = value
                console.log('reslove被调用');
            }
        }
        // 	第二步设置reject函数
        const reject = (reason) => {
            // 第四步状态设计,只有pending期间才可以调用
            if(this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED
                this.reason = reason
                console.log('rejected被调用');
            }
        }
        excutor(reslove,reject)
    }
}

const promise = new PHPromise((reslove,reject) => {
    reslove()
})

then方法设计

第一步实现在调用reslove/reject的时候,调用then的第一/二个回调函数,并且传递参数<br />    第二步调整函数onfulfilled,onrejected执行顺序,因为在new Promise的时候就已经调用reslove/reject<br />    而此时还没有执行then方法,所以onfulfilled,onrejected此时还没被赋值为函数,无法被调用,解决方法<br />    采用微任务的queueMicrotask调整执行顺序,先让then方法执行。
class PHPromise {
    constructor(excutor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        const reslove = (value) => {
            if(this.status === PROMISE_STATUS_PENDING) {
                // 设计一个微任务调整onfulfilled,onrejected执行
                queueMicrotask(() => {
                    if(this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    // 调用then传入的第一个参数函数
                    this.onfulfilled(this.value)
                })
            }
        }
        const reject = (reason) => {
            if(this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if(this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED
                    this.reason = reason
                    // 调用then传入的第二个参数函数
                    this.onrejected(this.reason)
                })
            }
        }
        excutor(reslove,reject)
    }
    // then方法设计
    then(onfulfilled,onrejected) {
        this.onfulfilled = onfulfilled
        this.onrejected = onrejected
    }
}

const promise = new PHPromise((reslove,reject) => {
    reslove(2222)
}).then(res => {
    console.log('res:',res);
},err => {
    console.log('err:',err);
})

优化一

解决then方法多次调用问题

思路:将每次then方法传入的函数装入数组中,然后再统一执行

解释:由于上面代码只能在onfulfilled/onrejected添加一个函数,在第二次调用then的时候会直接覆盖上一次的onfulfilled/onrejected函数

解决then方法在状态已经确认时的问题

思路:区分多种状态,设计在fulfilled rejected时直接调用reslove/reject

解释:在比如设置setimeout定时器时,将promise.then放入宏任务中延迟调用,所以在queueMicrotask执行回调函数时,宏任务里面的promise.then方法还未执行,回调函数还没传入onfulfilledFns/onrejectedFns,所以无法执行promise.then的回调函数

class PHPromise {
    constructor(excutor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        // 设计
        this.onfulfilledFns = []
        this.onrejectedFns = []

        const reslove = (value) => {
            if(this.status === PROMISE_STATUS_PENDING) {
                // 添加微任务
                queueMicrotask(() => {
                    // 状态决定
                    // 为了使只能执行resolve 或者 reject
                    if(this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    console.log('reslove被调用');
                    this.onfulfilledFns.forEach(fn => fn(this.value))
                })
            }
        }
        const reject = (reason) => {
            if(this.status === PROMISE_STATUS_PENDING) {
                // 添加微任务
                queueMicrotask(() => {
                    if(this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED
                    this.reason = reason
                    console.log('rejected被调用');
                    this.onrejectedFns.forEach(fn => fn(this.reason))
                })
            }
        }
        excutor(reslove,reject)
    }

    then(onfulfilled,onrejected) {
        // 多个状态解决方法
        // pending 就是在按正常时间添加进fns的时候
        if(this.status === PROMISE_STATUS_PENDING) {
            this.onfulfilledFns.push(onfulfilled)
            this.onrejectedFns.push(onrejected)
        }
        // fulfilled 到执行遍历调用onfulfilledFns方法时,还没把then的方法添加到onfulfilledFns里面
        if(this.status === PROMISE_STATUS_FULFILLED) {
            onfulfilled(this.value)
        }
        // rejected
         if(this.status === PROMISE_STATUS_REJECTED) {
            onrejected(this.reason)
        }
        
    }
}

const promise = new PHPromise((reslove,reject) => {
    reslove(2222)
})


// then方法多次调用 
// 思路:将then传入的回调函数放入数组中,再遍历调用

promise.then(res => {
    console.log('res1:',res);
},err => {
    console.log('err1:',err);
})


promise.then(res => {
    console.log('res2:',res);
},err => {
    console.log('err2:',err);
})


// 在状态确定后then再调用
setTimeout(() => {
    promise.then(res => {
    console.log('res3:',res);
},err => {
    console.log('err3:',err);
})
},1000)

优化二

解决then链式调用

思路:在then方法执行时返回一个promise,同时解决then方法中return和throw Error()的两种情况

return一个普通值的情况

then方法会返回一个promise包裹的值——这里只考虑返回值,不考虑返回promise以及带有then方法的Object,那么如何拿到上一个promise的返回值呢?需要我们分状态拿值
pending状态:因为then方法在上面的reslove以及reject函数里面调用,所以返回值也在上面
那么想要从上面拿的话有点难操作,所以采用函数包裹函数的方法解决
reslove reject状态:返回值直接拿

class PHPromise {
  
    then(onfulfilled,onrejected) {
        return new TTPromise((reslove,reject) => {
        // pending 就是在按正常时间添加进fns的时候
        if(this.status === PROMISE_STATUS_PENDING) {
            // pending拿返回值情况
            this.onfulfilledFns.push(() => {
                const value = onfulfilled(this.value)
                reslove(value)
            })
            this.onrejectedFns.push(() => {
                const reason = onrejected(this.reason)
                reslove(reason)
            })
        }

        // fulfilled rejected 
        if(this.status === PROMISE_STATUS_FULFILLED) {
          // reslove拿返回值情况
            const value = (this.value)
            reslove(value)
        }

         if(this.status === PROMISE_STATUS_REJECTED) {
          // reject拿返回值情况
            const reason = onrejected(this.reason)
            reslove(reason)
        }

        })
        
    }
}

解决throw问题

使用 try catch捕捉错误

then(onfulfilled,onrejected) {
        return new TTPromise((reslove,reject) => {
        // pending 就是在按正常时间添加进fns的时候
        if(this.status === PROMISE_STATUS_PENDING) {
            if(onfulfilled) this.onfulfilledFns.push(() => { 
                try {
                    const value = onfulfilled(this.value)
                    reslove(value)
                } catch(err) {
                    reject(err)
                }
            })
            if(onrejected) this.onrejectedFns.push(() => {
                try {
                    const reason = onrejected(this.reason)
                    reslove(reason)
                } catch(err) {
                    reject(err)
                }
            })
        }

        // fulfilled rejected 到执行遍历调用onfulfilledFns方法时,还没把then的方法添加到onfulfilledFns里面
        if(this.status === PROMISE_STATUS_FULFILLED) {
            try {
                const value = (this.value)
                reslove(value)
            } catch(err) {
                reject(err)
            }
        }

         if(this.status === PROMISE_STATUS_REJECTED) {
            try {
                const reason = onrejected(this.reason)
                reslove(reason)
            } catch(err) {
                reject(err)
            }
        }

        })
        
    }

由于try catch的代码块多次出现,我们将其抽取出来

function excFnWithResult(reslove,reject,value,fn) {
    try {
        const values = fn(value)
        reslove(values)
    } catch(err) {
        reject(err)
    }
}

then(onfulfilled,onrejected) {
        return new PHPromise((reslove,reject) => {
        // pending 就是在按正常时间添加进fns的时候
        if(this.status === PROMISE_STATUS_PENDING) {
            if(onfulfilled) this.onfulfilledFns.push(() => {
                excFnWithResult(reslove,reject,this.value,onfulfilled)
            })
            if(onrejected) this.onrejectedFns.push(() => {
                excFnWithResult(reslove,reject,this.reason,onrejected)
            })
        }
        // fulfilled
        if(this.status === PROMISE_STATUS_FULFILLED) {
            excFnWithResult(reslove,reject,this.value,onfulfilled)
        }
        // rejected
         if(this.status === PROMISE_STATUS_REJECTED) {
            excFnWithResult(reslove,reject,this.reason,onfulfilled)
        }
        })
        
    }

finally方法设计

思路

无论是reslove还是reject,都调用finally传入的回调函数

finally(fn) {
        this.then(() => {
            fn()
        },() => {
            fn()
        })
    }

catch方法设计

思路

catch方法本质上来说就是调用then方法第二个回调函数的语法糖,所以要实现catch,可以从调用then的第二个回调函数来下功夫。
可是直接在catch方法里面调用then方法,相当于是链式调用了then,then方法返回的新的promise,不是最初的promise,所以思路是——把then的第二个回调函数,变成thow 一个err丢出去,因为前面定义了,thow error
出去之后,再去.then就会到第二个回调函数

class PHPromise {
    then(onfulfilled,onrejected) {
        const defaultOnrejected = (err) => {throw err}
        onrejected = onrejected || defaultOnrejected

        // 以下代码不做改变,先做省略
    }

    catch(onrejected) {
        this.then(undefined,onrejected)
    }

}
const promise = new PHPromise((reslove,reject) => {
    reject(4444)
    // reslove(2222)
})

promise.then(res => {
    console.log('res1:',res);
    return 1111
}).catch(err => {
    console.log('catch的err:',err);
})

优化

在执行下列代码时,你觉得finally方法内部的回调函数会被调用吗?

const promise = new PHPromise((reslove,reject) => {
    reslove(2222)
})

promise.then(res => {
    console.log('res1:',res);
    return 1111
}).catch(err => {
    console.log('catch的err:',err);
}).finally(() => {
    console.log('finally~~~~~');
})

答案是 —— 不会的
因为我们在上面是没有处理调用then方法时,如果没有传递第一个参数会怎么样,那么也就不会将finally的回调函数push进onfulfilledFn或者onrejectedFns,最后onfulfilledFn/onrejectedFns遍历执行的时候当然也就不会有finally的回调函数参与了。
解决方法很简单——给then方法设定一个当第一个参数没传时的默认值

then(onfulfilled,onrejected) {
        const defaultOnrejected = (err) => {throw err}
        onrejected = onrejected || defaultOnrejected

        // 防止调用catch时res函数为undefined,所以每当undefined时,就要给一个默认函数,让她能继续返回上面函数的返回值
        const defaultOnfulfilled = (value) => { return value }
        onfulfilled = onfulfilled || defaultOnfulfilled
    }

all allSettle

思路:

确定什么时候调用reslove 什么时候调用 reject

static all(promises) {
        // 关键在于什么时候调用reslove 什么时候调用reject
        return new PHPromise((reslove,reject) => {
            const values = []
            promises.forEach((promise) => {
                promise.then(res => {
                    values.push(res)
                    if(values.length === promises.length) {
                        reslove(values)
                    }
                },err => {
                    reject(err)
                })
            })
        })
    }

    static allSettled(promises) {
        const values = []
        return new PHPromise((reslove) => {
            promises.forEach((promise) => {
                promise.then(res => {
                    values.push({ status: 'fulfilled', res })
                    if(values.length = promises.length) {
                    reslove(values)
            }
                },err => {
                    values.push({ status: 'rejected', err })
                    if(values.length = promises.length) {
                    reslove(values)
            }
                })
            })
            
        })
    }

race any

这个比较容易,看看就懂了

static race(promises) {
        return new PHPromise((reslove,reject) => {
            promises.forEach(promise => {
                promise.then(res => {
                    reslove(res)
                },err => {
                    reject(err)
                })
            })
        }) 
    }

    static any(promises) {
        const values = []
        return new PHPromise((reslove,reject) => {
            promises.forEach(promise => {
                promise.then(res => {
                    reslove(res)
                },err => {
                    values.push(err)
                    if(values.length === promises.length) {
                        return reject(new AggregateError(errors, 'All promises were rejected'))
                    }
                })
            })  
        })
    }

完结撒花,文中有什么错误的欢迎各位大神来纠正一下,大家理性探讨,共同进步