用原生js实现promise类

143 阅读6分钟

我们要用原生js实现Promise类的话,就得非常熟悉Promise 的规则,以及一些属性方法的应用。

一、搭建Promise

首先我们确定Promise使用过程中有三个状态,那么我们可以先声明三个常量来表示Promise的状态 如下所示:

 const PROMISE_STATUS_PENDING = 'pending'
 const PROMISE_STATUS_FULFILLED = 'fulfilled'
 const PROMISE_STATUS_REJECTED = 'rejected'

那么我们为什么要这么声明常量的命名格式呢,这是因为我遵循了Promise A+的规范

二、框架的搭建

我们先搭建Promise的框架,写一个新的类,名字就叫做zbPromise,如下所示

  
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_REJECTED = 'rejected'

class zbPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        const resolve = () => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_FULFILLED
                console.log('resolve被执行了');
            }
        }
        const reject = () => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED
                console.log('reject被执行了');
            }
        }
        executor(resolve, reject)
    }
}
const p1 = new zbPromise((resolve, reject) => {
    resolve('success')
})

Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数: 也就是这里的resolve和reject函数

image.png

可以运行上面的代码,在new zbPromise之后,执行resolve函数,执行结果如下:

image.png

三、then方法的实现

现在我们接着来实现then方法

const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_REJECTED = 'rejected'

class zbPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        const resolve = (value) => {
            if (this.status === PROMISE_STATUS_PENDING) {

                this.status = PROMISE_STATUS_FULFILLED
                this.value = value
                this.onFulfilled(this.value)
                console.log('resolve被执行了');


            }
        }
        const reject = (reason) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED
                this.reason = reason
                this.onRejected(this.reason)
                console.log('reject被执行了');
            }
        }
        executor(resolve, reject)
    }
    then(onFulfilled, onRejected) {
        this.onFulfilled = onFulfilled
        this.onRejected = onRejected
    }
}
const p1 = new zbPromise((resolve, reject) => {
    resolve('success')
})
p1.then(res => {
    console.log(res);
}, err => {
    console.log(err);
})

image.png

此时执行代码会发现报错,我们new Promise的时候,会自动执行constructor构造函数,但是当执行resolve(success')的时候,本应该是执行完resolve函数,然后由then回调执行的结果,但是它在执行resolve的时候就报错了,是因为resolve的时候此时并没有onFulfilled函数传进来,所以我们可以将整个resolve的执行逻辑包裹在微任务队列里面,这样一来就可以正常执行了。

正确代码:

const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_REJECTED = 'rejected'

class zbPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        const resolve = (value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    this.onFulfilled(this.value)
                    console.log('resolve被执行了');
                })

            }
        }
        const reject = (reason) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    this.status = PROMISE_STATUS_REJECTED
                    this.reason = reason
                    this.onRejected(this.reason)
                    console.log('reject被执行了');
                })

            }
        }
        executor(resolve, reject)
    }
    then(onFulfilled, onRejected) {
        this.onFulfilled = onFulfilled
        this.onRejected = onRejected
    }
}
const p1 = new zbPromise((resolve, reject) => {
    resolve('success')
})
p1.then(res => {
    console.log(res);
}, err => {
    console.log(err);
})

执行结果如下:

image.png

3.1then方法的优化1------实现promise实例的多次调用

虽然我们已经实现了promise的then方法,但是当我们多次调用的then的时候,它的最后一次结果会覆盖前面所有的结果,并且我们在new zbPromise函数的时候,同时调用reject函数的话,那么reject函数也会被执行,这与我们的意愿相互违背,按理来说,promise的状态在被修改以后是不会被再次改变的。于是我们有了一个想法,那就是在then方法中用两个函数数组来保存所要执行的函数,然后在resolve或者reject函数中将这两个数组进行遍历执行,然后在微任务队列函数中加入状态判断,如果不是pending状态,那就是直接return,不需要在继续执行下去。

const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_REJECTED = 'rejected'

class zbPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledFns = []
        this.onRejectedFns = []
        const resolve = (value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    this.onFulfilledFns.forEach(fn => {
                        fn(this.value)
                    })
                    console.log('resolve被执行了');
                })

            }
        }
        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
                    this.onRejectedFns.forEach(fn => {
                        fn(this.reason)
                    })
                    console.log('reject被执行了');
                })

            }
        }
        executor(resolve, reject)
    }
    then(onFulfilled, onRejected) {
        // this.onFulfilled = onFulfilled
        // this.onRejected = onRejected
        this.onFulfilledFns.push(onFulfilled)
        this.onRejectedFns.push(onRejected)
    }
}
const p1 = new zbPromise((resolve, reject) => {
    resolve('success')
    //当同时出现的时候,谁先执行,就先回调谁的结果,且只执行一次
    reject('failure')
})
p1.then(res => {
    console.log(res);
}, err => {
    console.log(err);
})
p1.then(res => {
    console.log(res);
}, err => {
    console.log(err);
})

执行结果:

image.png

3.2 then的优化2-----执行完一次以后,我们直接执行对应的函数

如果在我们then调用的时候,状态已经确定下来了,那么我们直接执行对应的函数即可

const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_REJECTED = 'rejected'

class zbPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledFns = []
        this.onRejectedFns = []
        const resolve = (value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    this.onFulfilledFns.forEach(fn => {
                        fn(this.value)
                    })
                    console.log('resolve被执行了');
                })

            }
        }
        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
                    this.onRejectedFns.forEach(fn => {
                        fn(this.reason)
                    })
                    console.log('reject被执行了');
                })

            }
        }
        executor(resolve, reject)
    }
    then(onFulfilled, onRejected) {
        // this.onFulfilled = onFulfilled
        // this.onRejected = onRejected
        // 如果状态已经是fulfilled了,那么就直接执行onfulfilled
        if (this.onFulfilled && this.status === PROMISE_STATUS_FULFILLED) {
            onFulfilled(this.value)
        }
        if (this.onRejected && this.status === PROMISE_STATUS_REJECTED) {
            onRejected(this.reason)
        }
        if (this.status === PROMISE_STATUS_PENDING) {
            this.onFulfilledFns.push(onFulfilled)
            this.onRejectedFns.push(onRejected)
        }

    }
}
const p1 = new zbPromise((resolve, reject) => {
    resolve('success')
    reject('failure')
})
p1.then(res => {
    console.log(res);
}, err => {
    console.log(err);
})
p1.then(res => {
    console.log(res);
}, err => {
    console.log(err);
})

3.3 then优化3----链式调用(then的时候返回新的promise)

const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_REJECTED = 'rejected'

class zbPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledFns = []
        this.onRejectedFns = []
        const resolve = (value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    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
                    this.onRejectedFns.forEach(fn => {
                        fn(this.reason)
                    })

                })

            }
        }
        executor(resolve, reject)
    }
    then(onFulfilled, onRejected) {
        // this.onFulfilled = onFulfilled
        // this.onRejected = onRejected
        // 如果状态已经是fulfilled了,那么就直接执行onfulfilled
        return new zbPromise((resolve, reject) => {
            if (this.onFulfilled && this.status === PROMISE_STATUS_FULFILLED) {
                try {
                    const value = onFulfilled(this.value)
                    resolve(value)
                } catch (error) {
                    reject(error)
                }
            }
            if (this.onRejected && this.status === PROMISE_STATUS_REJECTED) {
                try {
                    const reason = onRejected(this.reason)
                    resolve(reason)
                } catch (error) {
                    reject(error)
                }
            }
            if (this.status === PROMISE_STATUS_PENDING) {
                this.onFulfilledFns.push(() => {
                    try {
                        const value = onFulfilled(this.value)
                        resolve(value)
                    } catch (error) {
                        reject(error)
                    }
                })
                //我们将函数的用trycatch代码块包裹起来,拿到某一状态的执行结果,将结果返回传入到相应的resolve和reject中
                this.onRejectedFns.push(() => {
                    try {
                        const reason = onRejected(this.reason)
                        resolve(reason)
                    } catch (error) {
                        reject(error)
                    }
                })
            }
        })


    }
}
const p1 = new zbPromise((resolve, reject) => {
    resolve('success')
    reject('failure')
})
p1.then(res => {
    console.log(res);
    // throw new Error('我第一次执行出错了')
    return 'aaaaaa'
}, err => {
    console.log(err);
}).then(res => {
    console.log(res);
}, err => {
    console.log(err);
})





p1.then(res => {
    console.log(res);
}, err => {
    console.log(err);
})

四、catch方法的实现

由于是catch方法,能够回调出reject的结果,那么catch方法的函数体中我们可以用then来实现, 比如

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

这样能行吗?理论上是可行的,但是这样虽然能执行catch方法,但是输出为undefined,因为catch方法相当于调用了一个新的promise,而我们的目的是回调前一个Promise的失败的回调,我们该怎么解决呢?

那么我们可以直接将第一个promise失败的回调直接抛出异常,那么第二个刚好可以执行; 于是我们在then方法中加入 onRejected = onRejected || (err => { throw err }) 也就是如果第一个Promise的onRejected为undefined的话,那么我们就抛出异常;

五、finally方法的实现

不管是成功还是失败,都会调用finally方法,那么就简单实现了(注意在此之前一定要把catch方法里面的值返回,finally用于接收)

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

    }

这样一来的话,我们就必须在then方法中加入onFulfilled判断,由于finally方法下相当于第三个promise回调,所以我们要将前面then方法中加入 onFulfilled = onFulfilled || (value => value) onFulfilled为空的话,就返回第一个promise执行结果。

六、resolve方法和reject方法

直接返回promise即可,调用对应的方法,注意这两个是类方法,不是实例方法

static resolve(value) {
        return new zbPromise((resolve) => { resolve(value) })
    }
    static reject(reason) {
        return new zbPromise(reject => reject(reason))
    }

七、all方法

执行promise数组,其中只要不是reject状态,那么就会执行,否则就会停止,调用catch方法;

static all(promises) {
        return new zbPromise((resolve, reject) => {
            const values = []
            promises.forEach(promise => {
                promise.then(res => {
                    values.push(res)
                    if (values.length === promise.length) {
                        resolve(values)
                    }
                    // console.log(res);
                }, err => {
                    // console.log(err);
                    reject(err)
                })
            })
        })
    }

八、allsettled方法

不管什么状态,都会输出回调结果

static allSettled(promises) {
        return new zbPromise((resolve, reject) => {
            const values = []
            promises.forEach(promise => {
                promise.then(res => {
                    values.push({ status: PROMISE_STATUS_FULFILLED, result: res })
                    if (values.length === promises.length) {
                        resolve(values)
                    }
                }, err => {
                    values.push({ status: PROMISE_STATUS_REJECTED, result: err })
                    if (values.length === promises.length) {
                        resolve(values)
                    }
                })
            })
        })
    }

image.png

image.png