你真的理解Promise吗?带你简单实现一个Promise

129 阅读11分钟

本文将带领大家实现一个简单的Promise,在阅读本文之前,默认您对Promise有基本的了解,会基本的使用,以及对ES6的语法有一定的了解

1. Promise的构造函数

首先,我们从Promise的构造函数开始,这是使用Promise最关键的一步,先直接上代码

// 创建三个常量,用于表示Promise的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// Promise类
class Promise {
    // Promise构造函数会传入一个方法
    constructor(executor) {
        // Promise中存在一个保存状态和保存状态结果的变量
        this.promiseState = PENDING
        this.promiseResult = null
        // 创建Promise要传入的两个函数,用于将状态变为成功和失败的函数
        const resolve = res => {
            // 我们使用了箭头函数,方便绑定this
            // 成功的函数,用于将Promise状态修改为FULFILLED,并保存成功值
            // 我们知道,如果Promise状态已改变,就不能再改变了,所以要做一个检测
            if (this.promiseState !== PENDING) return
            this.promiseState = FULFILLED
            this.promiseResult = res
        }
        const reject = err => {
            if (this.promiseState !== PENDING) return
            this.promiseState = REJECTED
            this.promiseResult = err
        }
        // 最后,我们需要调用executor,传入两个改变状态的函数,并进行异常处理
        try {
            executor(resolve, reject)
        } catche (err) {
            // 出现异常,我们就调用reject函数,改变Promise状态为失败即可
            reject(err)
        }
    }
}
  • 对于Promise构造函数,使用过的同学都知道,我们需要传递一个函数,并且内部会为这个函数执行时传入两个参数,第一个是解决执行的函数,第二个是失败执行的函数,并且向resolve或reject传递参数,参数的值将会成为Promise状态变更后的结果值。
  • 按照这个思想,我们需要在构造函数中实现的就是resolve与reject函数,这两个函数的值其实就是用来改变当前promise的状态,并保存状态改变时的参数,对于已改变的状态,不会再次改变(Promise的含义,承诺、期约)。
  • 上面,我们就实现了一个基本的构造函数,但是,上面还只能处理executor是同步改变状态的情况,如果是异步调用的resolve或reject,则不行,我们后续在编写then函数时会完善这个功能

2. 静态方法resolve

因为在then函数中,我们将使用这个方法,所以首先编写这个函数 在编写之前,我们要知道Promise.resolve函数对于不同传入参数的处理,这个函数是用来包装一个值称为Promise对象,但根据不同的参数值传入,会进行不同的处理

1、传入的是一个Promise对象
如果传入的是一个Promise对象,那么将会幂等的返回一个Promise对象,其实意思就是返回这个Promise对象本身

2、传入的是一个thenable对象
thenable对象的含义就是,对象中拥有then方法,此时,这个then方法就跟Promise构造函数传入的executor拥有同样的功能,即这个then方法在参数中会分别接收到resolve和reject方法,可以用来改变Promise对象的状态

3、传入其他的值
如果不是以上两种类型,Promise会创建一个新的Promise对象,将这个新Promise的状态改变为fulfilled,并将状态结果设置为传入的值,即Promise会包装这个值

有了以上的分析,我们就来写一写这个方法

class Promise {
    static resolve(wrapped) {
        if (wrapped instanceof Promise) {
            // 如果是Promise对象
            return wrapped
        } else if (wrapped instanceof Object && wrapped.then instanceof Function) {
            // 是一个thenable对象,then方法就相当于executor函数
            return new Promise(wrapped.then)
        } else {
            // 其他值直接包装
            return new Promise(resolve => resolve(wrapped))
        }
    }
}

当然,上面代码如果要正常运行,得在我们的Promise实现完成之后,因为代码中使用到了Promise,我们之所以先编写这个函数,是为了方便then函数的编写

3. 静态方法reject

既然我们都编写了resolve,那干脆reject的一起写了吧,reject并不像resolve函数一样,resolve函数是幂等的,但reject函数,不论接收到什么值,都会将这个值包装成一个拒绝的Promise对象,话不多说,直接写上代码

class Promise {
    static reject(wrapped) {
        return new Promise((resolve, reject) => reject(wrapped))
    }
}

一行代码就搞定,非常简单对吧

4. then方法(Promise的核心)

上面都只是开胃菜,then方法才是我们的重中之重,在书写then方法前,我们先理一理then方法中的一些规则

  1. 首先,then方法可以传入两个方法,第一个是解决执行的回调,第二个是拒绝执行的回调
  2. then方法返回一个Promise对象,根据解决或拒绝的回调函数的返回值决定这个返回的Promise对象的状态
  3. 解决或拒绝函数中的异常,会导致返回的Promise对象为拒绝状态
  4. 值传递和异常穿透(后面写的时候会解释)

接下来,我们以代码注释的形式,在代码中将这些功能体现出来,包括一些异步状态改变处理,都会在代码注释中解释

class Promise {
    // 首先是传入参数,一共是两个,实现了我们说的第一条规则
    then(onFulfilled, onRejected) {
        // 这里创建了一个新的Promise对象,作为函数的返回值
        // 这个Promise对象的状态,由参数的两个处理函数的返回值决定
        // 我们在函数最后返回了这个值,实现了上述的第二条规则
        const resultPromise = new Promise((resolve, reject) => {
            // 此处便实现了第三条规则的值传递
            // 如果传入的解决的回调函数参数不是一个函数,
            // 那么返回的Promise对象(resultPromise)的状态和值便需要同步当前then函数对应的Promise
            if (typeof onFulfilled !== 'function') {
                onFulfilled = resolve
            }
            // 实现异常穿透跟值传递是同样的道理
            if (typeof onRejected !== 'function') {
                onRejected = reject
            }
            // 接下来就是对于这个两个回调函数的调用
            if (this.promiseState === PENDING) {
                // 建议可以先看一下下面解决和拒绝状态的处理,再来看等待状态
                // 如果执行到then方法,状态还是等待,说明状态是异步改变的
                // 此时我们就需要把成功和失败的回调保存下来,等到状态改变的时候再调用
                // 我们就可以在构造函数中多创建一个数组,用来专门保存这个要异步执行的回调函数
                // 需要在构造函数中改变的代码,将在then方法后面写出
                this.promiseCallbacks.push({
                    // 这个onFulfilled只是对象的键名,不要和then传入的onFulfilled混淆了
                    onFulfilled: () => {
                        let value
                        try {
                            // 此处调用的是then传入的onFulfilled
                            value = onFulfilled(this.promiseResult)
                        } catch (err) {
                            // 如果出现异常,那么就把返回的Promise的状态同步为拒绝
                            reject(err)
                        }
                        Promise.resolve(value).then(resolve, reject)
                    },
                    onRejected: () => {
                        let value
                        try {
                            // 此处调用的是then传入的onRejected
                            value = onRejected(this.promiseResult)
                        } catch (err) {
                            // 如果出现异常,那么就把返回的Promise的状态同步为拒绝
                            reject(err)
                        }
                        Promise.resolve(value).then(resolve, reject)
                    }
                })
            } else if (this.promiseState === Promise.FULFILLED) {
                // 如果在执行then方法中回调时,状态是解决,直接执行回调
                // 当然执行回调要进行异常处理,以及返回Promise状态的同步
                let value
                try {
                    value = onFulfilled(this.promiseResult)
                } catch (err) {
                    // 如果出现异常,那么就把返回的Promise的状态同步为拒绝
                    reject(err)
                }
                /**
                * 因为then方法返回的Promise值,是由onFulfilled或onRejected决定的
                * 并且对于onFulfilled和onRejected返回值拥有与静态方法resolve同样的包装规则
                * 所以可以使用Promise.resolve来包装返回值,再进行最终返回状态的同步
                */
                Promise.resolve(value).then(resolve, reject)
            } else if (this.promiseState === Promise.REJECTED) {
                    // 状态为拒绝的情况,跟上面一样,除了调用的回调函数不一样
                let value
                try {
                    value = onRejected(this.promiseResult)
                } catch (err) {
                    reject(err)
                }
                Promise.resolve(value).then(resolve, reject)
            }
        })
        return resultPromise
    }

    // 这里是上述改变后的构造函数的代码,我们删除了不必要的注释
    constructor(executor) {
        this.promiseState = PENDING
        this.promiseResult = null
        // 这里就加入一个保存回调的数组,用于保存回调函数
        // 您可能会疑惑为什么要用一个数组来保存
        // 因为一个Promise对象可以多次调用then方法,所以就可以又多个回调函数需要保存
        this.promiseCallbacks = []
        const resolve = res => {
            if (this.promiseState !== PENDING) return
            this.promiseState = FULFILLED
            this.promiseResult = res
            // 成功的处理就应该调用所有成功的回调函数
            this.promiseCallbakcs.forEach(item => item.onFulfilled())
        }
        const reject = err => {
            if (this.promiseState !== PENDING) return
            this.promiseState = REJECTED
            this.promiseResult = err
            // 失败的处理就应该调用所有失败的回调函数
            this.promiseCallbakcs.forEach(item => item.onRejected())
        }
        try {
            executor(resolve, reject)
        } catche (err) {
            reject(err)
        }
    }
}

看了上面代码,咱们可能发现,有需要重复的代码,比如在处理异常和同步状态的地方,有些非常高的相似度,所以我们将代码优化一下,将重复的地方封装一下

// 首先,我们也是将不必要的注释删除,并将能单行书写的代码也改为单行
class Promise {
    then(onFulfilled, onRejected) {
        const resultPromise = new Promise((resolve, reject) => {
            // 此处我们写一个函数,整合异常处理和返回值同步的地方
            // 参数handler代码要执行的函数,如onFulfilled或onRejected
            const resultHandler = handler => {
                // 这里使用一个setTimeout,就是模拟了then方法回调函数的异步执行
                // 在Promise中then方法的回调函数是异步的微任务,我们这里用这个模拟一下异步
                setTimeout(() => {
                    // 这里面的结构就跟之前的处理是一样的
                    // 只是把要调用的函数,换成了传入的handler
                    let value
                    try {
                        value = handler(this.promiseResult)
                    } catch (err) {
                        reject(err)
                    }
                    // 这里我们加入一个循环引用的检测
                    // 防止then回调函数中返回自己返回的Promise对象
                    if (callbackReturn === returnPromise) {
                        throw new TypeError('Chaining cycle detected for promise #<Promise>')
                    }
                    Promise.resolve(value).then(resolve, reject)
                })
            }
            if (typeof onFulfilled !== 'function ') onFulfilled = resolve
            if (typeof onRejected !== 'function ') onRejected = reject
            if (this.promiseState === PENDING) {
                this.promiseCallbacks.push({
                    // 下面这些地方就会变得很简单,只能传入对应的处理函数即可
                    onFulfilled: () => resultHandler(onFulfilled)
                    onRejected: () => resultHandler(onRejected)
                })
            } else if (this.promiseState === Promise.FULFILLED) {
                resultHandler(onFulfilled)
            } else if (this.promiseState === Promise.REJECTED) {
                resultHandler(onRejected)
            }
        })
        return resultPromise
    }
}

到这里,是不是发现其实也不难,只要对Promise功能有一定的了解,相信大家自己总结一下也能够将它写出来

5. catch方法

有了上面的函数,剩下的方法实现起来都会变得非常容易

class Promise {
    catch(onRejected) {
        // catch方法其实就是用来处理异常穿透下来的异常,所以它跟then方法中onRejected的功能是一样的
        // 所以我们可以直接使用then方法来完成它的功能
        return this.then(null, onRejected)
    }
}

6. finally方法

class Promise {
    finally(onFilnally) {
        // finally也同样,但是无论解决还是拒绝,都调用同样的方法
        // 但是finally回调函数不需要传入值,所以使用一个箭头函数包装一下
        return this.then(() => onFilnally(), () => onFilnally())
    }
}

7. 静态方法all

all方法可以接收一个数组,其中的值如果是Promise对象,则只有当所有对象是解决状态时,这个all方法才会返回一个解决的值,如果有任何一个Promsie对象被拒绝,那么all方法返回的Promise也是拒绝的,并且拒绝理由就是这个被拒绝的Promise的理由,如果参数数组中有的元素不是Promise对象,则会被包装成Promise对象

class Promise {
    static all(promises) {
        // 我们就默认传入的是数组,省去一些检查的步骤
        return new Promise((resolve, reject) => {
            // 放置结果的数组
            let ret = []
            // 用于判断是否数组中所有promise对象都处理完毕
            let count = 0
            // 我们就应该遍数组,去处理数组中的每一个Promise对象
            for (let index = 0; index < promises.length; ++index) {
                // 我们使用Promise.resolve包装值,让所有元素都称为Promise对象
                Promise.resolve(promises[index]).then(
                    res => {
                        // 如果成功,存储其成功值
                        count++
                        ret[index] = res
                        if (count === promises.length) {
                            // 如果全部都解决,则返回的Promise为解决
                            resolve(ret)
                        }
                    }, err => {
                        // 如果有任何一个失败,则返回的Promise为失败
                        reject(err)
                    }
                )
            }
        })
    }
}

8. 静态方法race

race方法相对all就更加简单,谁快就是谁

class Promise {
    static race(promises) {
        // 我们就默认传入的是数组,省去一些检查的步骤
        return new Promise((resolve, reject) => {
            for (let index = 0; index < promises.length; ++index) {
                Promise.resolve(promises[index]).then(
                    res => {
                        resolve(res)
                    }, err => {
                        reject(err)
                    }
                )
            }
        })
    }
}

9. 整理所有代码

下面列出所有代码的整合,删除了注释

class Promise {
    constructor(executor) {
        this.promiseState = PENDING
        this.promiseResult = null
        this.promiseCallbacks = []
        const resolve = res => {
            if (this.promiseState !== PENDING) return
            this.promiseState = FULFILLED
            this.promiseResult = res
            this.promiseCallbakcs.forEach(item => item.onFulfilled())
        }
        const reject = err => {
            if (this.promiseState !== PENDING) return
            this.promiseState = REJECTED
            this.promiseResult = err
            this.promiseCallbakcs.forEach(item => item.onRejected())
        }
        try {
            executor(resolve, reject)
        } catche (err) {
            reject(err)
        }
    }

    then(onFulfilled, onRejected) {
        const resultPromise = new Promise((resolve, reject) => {
            const resultHandler = handler => {
                setTimeout(() => {
                    let value
                    try {
                            value = handler(this.promiseResult)
                    } catch (err) {
                            reject(err)
                    }
                    if (callbackReturn === returnPromise) {
                        throw new TypeError('Chaining cycle detected for promise #<Promise>')
                    }
                    Promise.resolve(value).then(resolve, reject)
                })
            }
            if (typeof onFulfilled !== 'function ') onFulfilled = resolve
            if (typeof onRejected !== 'function ') onRejected = reject
            if (this.promiseState === PENDING) {
                this.promiseCallbacks.push({
                    onFulfilled: () => resultHandler(onFulfilled)
                    onRejected: () => resultHandler(onRejected)
                })
            } else if (this.promiseState === Promise.FULFILLED) {
                resultHandler(onFulfilled)
            } else if (this.promiseState === Promise.REJECTED) {
                resultHandler(onRejected)
            }
        })
        return resultPromise
    }

    static resolve(wrapped) {
        if (wrapped instanceof Promise) {
            return wrapped
        } else if (wrapped instanceof Object && wrapped.then instanceof Function) {
            return new Promise(wrapped.then)
        } else {
            return new Promise(resolve => resolve(wrapped))
        }
    }

    static reject(wrapped) {
        return new Promise((resolve, reject) => reject(wrapped))
    }

    catch(onRejected) {
        return this.then(null, onRejected)
    }

    finally(onFilnally) {
        return this.then(() => onFilnally(), () => onFilnally())
    }

    static all(promises) {
        return new Promise((resolve, reject) => {
            let ret = []
            let count = 0
            for (let index = 0; index < promises.length; ++index) {
                Promise.resolve(promises[index]).then(res => {
                    count++
                    ret[index] = res
                    if (count === promises.length) resolve(ret)
                }, err => {
                    reject(err)
                })
            }
        })
    }

    static race(promises) {
        return new Promise((resolve, reject) => {
            for (let index = 0; index < promises.length; ++index) {
                Promise.resolve(promises[index]).then(res => {
                    resolve(res)
                }, err => {
                    reject(err)
                })
            }
        })
    }
}

10. 总结

到此为止,我们就实现了一个简单版本的Promise,相信在大家思考和练习之后,会发现,其实也不难嘛,也希望大家能从文章中学到东西,如果文章中有任何错误的地方,望各位大牛们能指点,谢谢大家。