关于JavaScript的Promise手写实现

113 阅读10分钟
1- 注释版代码
         /**
         * 在开始实现Promise之前  我们需要了解一些Promise的基本标准(作者的个人总结,权威详情可以去官方的promiseA+规范了解)
         * 1-   Promise   是一个构造函数  可以通过 new 关键字  获取该Promise对象的信息
         * 2-   Promise   存在三个状态 默认为pending(等待状态) fulfilled(承诺状态) rejected(拒绝状态)
         * 3-   Promise   状态只允许更改一次 一旦改变不再接收更改
         * 4-   Promise   构造函数 接收一个回调函数(executor) executor函数又接收两个回调入参  resolve成功回调  和 reject失败回调
         * 5-   Promise   的实例 存在两个方法可以调用 then 和 catch
         * 6-   then方法  可以实现链式调用 如 Promise实例.then().then().then()......
         * 7-   then方法  接收两个回调函数入参 第一个回调获取 fulfilled(承诺状态)返回的值 第二个回调获取 rejected(拒绝状态)返回的拒绝原因
         * 8-   then方法  两个入参可以选择是否传入
         * 9-   new Promise executor方法是同步执行
         * 10-  实例.then() 方法执行 是微任务
         * */

        /**
         * 实现Promise注意事项
         * 1- then为链式调用  因此then方法需要返回一个新的Promise对象
         * 2- then方法是同步执行
         * 3- then方法 与 resolve成功回调和reject失败回调  的执行关系为异步,因此会涉及到 订阅发布模式    
         * 4- then方法 会返回一个新的Promise实例 才能满足then的链式调用 
         * */


        function MyPromise(executor) {
            /**
             * 定义Promise实例的基本数据
             * 1- state  实例的状态  对应文章开始的Promise的基本标准2
             * 2- value  Promise执行结束的值  
             * 3- onFulfilledCallbacks  订阅数组,保存then方法的第一个入参回调函数   对应文章开始的Promise的基本标准7  注意事项3
             * 4- onRejectedCallbacks   订阅数组,保存then方法的第二个入参回调函数   对应文章开始的Promise的基本标准7  注意事项3
             * */
            this.state = 'pending';
            this.value = null;
            this.onFulfilledCallbacks = [];
            this.onRejectedCallbacks = [];

            /**
             * 定义resolve函数 以回调使用
             * Promise构造函数接收的回调函数入参 所接收的两个回调函数入参 resolve和reject  对应文章开始的Promise的基本标准4
             * 只在promise实例状态为pending时执行 对应文章开始的Promise的基本标准3
             * 功能处理1  改变实例状态 pending->fulfilled 
             * 功能处理2  将处理成功的值保存在实例的value属性里
             * 功能处理3  发布数组 执行所有保存then方法的第一个回调函数 将value返回出去    对应文章开始的Promise的基本标准7  注意事项3
             * */
            this.resolve = (res) => {
                if (this.state === 'pending') {
                    setTimeout(() => {
                        this.state = 'fulfilled';
                        this.value = res;
                        this.onFulfilledCallbacks.forEach(callback => {
                            callback(this.value)
                        })
                    });
                }
            }

            /**
             * 定义resolve函数 以回调使用
             * Promise构造函数接收的回调函数入参 所接收的两个回调函数入参 resolve和reject 对应文章开始的Promise的基本标准4
             * 只在promise实例状态为pending时执行 对应文章开始的Promise的基本标准3
             * 功能处理1  改变实例状态 pending->rejected 
             * 功能处理2  将拒绝的原因(错误原因)保存在实例的value属性里
             * 功能处理3  发布数组 执行所有保存then方法的第二个回调函数 将value返回出去   对应文章开始的Promise的基本标准7  注意事项3
             * */
            this.reject = (err) => {
                if (this.state === 'pending') {
                    setTimeout(() => {
                        this.state = 'rejected';
                        this.value = err;
                        this.onRejectedCallbacks.forEach(callback => {
                            callback(this.value)
                        })
                    });
                }
            }

            /**
             * 执行Promise构造函数接收的回调函数入参 
             * 用户的代码执行可能会存在错误 这里使用try-catch,没问题则执行,有问题则通过reject回调函数返回错误原因
             * */
            try {
                executor(this.resolve, this.reject);
            } catch (e) {
                this.reject(e)
            }
        }
        /**
        * catch的实现并不难,相当与then第一个参数为null的情况
        * 例子请参照 案例3和案例4
        */ 
        MyPromise.prototype.catch = function (error) {
            return this.then(null, error);
        }
        MyPromise.prototype.then = function (res, err) {
            /**
            * res, err 两个入参 用户可以选择是否传入 因此需要兼容未传入的情况  对应文章开始的Promise的基本标准8
            * res 用户没有传入的情况下  需要兼容成一个回调函数  配合  resolve 将实例value 返回给用户使用
            * err 用户没有传入的情况下  需要兼容成一个回调函数  配合  reject  将失败原因  返回给用户使用
            * */
            res = typeof res === 'function' ? res : value => value;
            err = typeof err === 'function' ? err : reason => {
                throw reason;
            };


            /**
             *  新建一个Promise对象 并返回它的实例 用于链式调用 对应实现Promise注意事项1
            * */
            const promise = new MyPromise((resolve, reject) => {
                /**
                 * 执行到这  开始执行对promise实例对象的处理 通过判断promise实例的状态 state 选择对应的处理
                * */
                if (this.state === 'fulfilled') {
                    /**
                     * 1-  promise实例的状态为fulfilled 代表promise执行承诺状态 调用了resolve 
                     * 2-  当then(a,b)a函数里 用户可能会return一些东西 也许是原始值,引用值, 比如一个新的Promise 
                     * 3-  为了能够兼容处理用户所return的各种可能性  我们需要进行再一次深入的判断
                     * 4-  所以这里调用了一个自己定义的函数方法 resolvePromise 进行统一处理
                     * 5-  resolvePromise接受4个入参 
                     * 6-  then()返回的新promise实例(后续在resolvePromise定义的地方会解释 为什么需要传入这个对象);
                     * 7-  x 也就是res(this.value)的结果  即此注释的第2点  resolvePromise的存在就是为了处理 x 这个值
                     * 8-  then()所返回的promise依然存在两个回调入参  第一个入参 resolve 负责将resolvePromise处理 x 的结果 返回给用户使用(该判断使用第一个入参 resolve功能)
                     * 9-  then()所返回的promise依然存在两个回调入参  第二个入参 reject  负责将resolvePromise处理 x 的所产生的错误 返回给用户使用
                     * 10- setTimeout存在意义, resolvePromise 的第一个入参 便是当前的promise实例,而此时promise尚未执行完毕,便无法将它作为入参使用
                     *       因此需要套上一个setTimeout,变成宏任务,等待当前执行栈代码执行完毕以后,才会执行此宏任务,届时promise已执行完毕,可当做入参使用
                    * */
                    setTimeout(() => {
                        try {
                            let x = res(this.value);
                            this.resolvePromise(promise, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    });
                } else if (this.state === 'rejected') {
                    /**
                     * rejected 状态与上面fulfilled 执行机制同理 只不过不执行第8条注释 而是执行第9条注释
                     * */
                    setTimeout(() => {
                        try {
                            let x = err(this.value);
                            this.resolvePromise(promise, x, resolve, reject);
                        } catch (e) {
                            reject(e)
                        }
                    });
                } else if (this.state === 'pending') {
                    /**
                     * 之前我们说过 then()方法和[resolve()-reject()]方法 是异步执行
                     * 比如案例1 then会先执行(可在then定义的第一行debugger调试查看)
                     * 这种情况 如果then里面直接获取实例的value,那肯定是undefined
                     * 因为resolve('成功')尚未执行 成功 这个值尚未赋值给实例的value
                     * 因此我们需要先将处理函数push进数组保存起来,待resolve('成功')执行后 再调用
                     * 这就是发布订阅模式 对应实现Promise注意事项3
                     * */
                    this.onFulfilledCallbacks.push(() => {
                        setTimeout(() => {
                            try {
                                let x = res(this.value);
                                this.resolvePromise(promise, x, resolve, reject)
                            } catch (e) {
                                reject(e);
                            }
                        });
                    });
                    this.onRejectedCallbacks.push(() => {
                        setTimeout(() => {
                            try {
                                let x = err(this.value);
                                this.resolvePromise(promise, x, resolve, reject);
                            } catch (e) {
                                reject(e);
                            }
                        });
                    });
                }
            })
            return promise
        }
        /**
        * 定义resolvePromise 作用是处理 then(cb) cb返回的值
        */
        MyPromise.prototype.resolvePromise = function (promise, x, resolve, reject) {
            /**
            * 第一个参数promise 的作用 衔接上面入参详解
            * 判断promise 和 then(cb) cb返回的值 是否为同一个,避免死循环 promiseA+规范有规定
            * 如果相同 则停止该函数 并返回一个错误信息
            * 如案例2
            */
            if (x === promise) {
                return reject(new TypeError('Chaining cycle detected for promise'));
            }

            /**
            * 判断x 是否为promise
            */
            if (x instanceof MyPromise) {
                if (x.state === 'pending') {
                    /**
                    * x的状态为pending,说明实例的状态和值没有改变
                    * 这个时候不能去获取 x 实例的值  因此我们沿用之前的发布订阅模式,走一边then函数
                    * 把then里面的两个回调函数 保存入数组  当实例对应的resolve或者rejectz执行时
                    * 自然会执行我们所保存的函数 将实例的value放入y,然后再走一遍resolvePromise,进行对y值的判断和处理
                    */
                    x.then(y => {
                        this.resolvePromise(promise, y, resolve, reject)
                    }, reject);
                } else if (x.state === 'fulfilled') {
                    /**
                    * x的状态如果不为pending,说明实例的状态和值已经改变
                    * 可以直接根据状态  进行resolve 和 reject处理
                    */
                    resolve(x.value);
                } else if (x.state === 'rejected') {
                    /**
                    * x的状态如果不为pending,说明实例的状态和值已经改变
                    * 可以直接根据状态  进行resolve 和 reject处理
                    */
                    reject(x.value);
                }
            } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
                /**
                * x 不是promise 但可能是一个函数或者普通对象 
                * 关于try-catch就不多说  本文的try-catch功能存在的作用都一致
                */
                try {
                    var then = x.then;
                } catch (e) {
                    return reject(e);
                }

                /**
                * 这里开始 可能会有人疑惑了 为什么还需要进行这么多的判断和控制 直接resolve或者reject不好吗 判断then的意义是什么
                * 因为这里还需要判断x是否是一个promise 听起来是不是很懵 之前不是 (x instanceof MyPromise) 判断过了吗
                * 很可惜的说 (x instanceof MyPromise) 不能判断所有promise 
                * 因为有些库也有自己写的promise, 比如我们现在这个写的叫做 MyPromise
                * 那怎么判断就没有其他的promise呢 比如 YourPromise HisPromise 等等......
                * 那怎么办  只能凉拌了!
                * 只能把拥有then方法的对象(所谓的thenable对象)都当成是一个promise执行了......  
                * 所以x.then 如果真的存在,且是一个函数,那就执行它,并且把this指向转移 x 
                * 关于then的执行和两个y,r入参 就不多说了 与上面描述的是一致的
                * 需要注意的是  called 的作用
                * promiseA+规范说明了 如果new Promise的过程中 用户同时使用 resolve和reject
                * 那么只能执行一个 called 的作用就是上一个锁,resolve和reject一旦某个执行 变锁死不会执行另一个方法
                */

                if (typeof then === 'function') {
                    let called = false;
                    try {
                        then.call(
                            x,
                            y => {
                                if (called) return;
                                called = true;
                                this.resolvePromise(promise, y, resolve, reject);
                            },
                            r => {
                                if (called) return;
                                called = true;
                                reject(r);
                            }
                        )
                    } catch (e) {
                        if (called) return;
                        called = true;
                        reject(e);
                    }
                } else {
                    resolve(x);
                }
            } else {
                /**
                * 说明 x 为基本数据类型  可以直接resolve(x)
                */
                resolve(x);
            }
        }

        /**
         * 案例1
         * resolve 在延时一秒后才会改变 then会先执行
         * */
        // const s = new MyPromise((resolve, reject) => {
        //     setTimeout(() => {
        //         resolve('成功');
        //     }, 1000)
        // })
        // s.then(res => {
        //     console.log(res);//undefined
        // })

        /**
         * 案例2
         * */
        // const s = new MyPromise((resolve, reject) => {
        //     setTimeout(() => {
        //         resolve('成功');
        //     }, 1000)
        // })
        // const p = s.then(res => {
        //     return p;
        // })
        /**
         * 案例3
         * */
        // const s = new MyPromise((resolve, reject) => {
        //     resolve(100);
        //     throw Error('错误报错')
        // })
        // s.then(null, err => {
        //     console.log('失败', err)
        // })
        /**
         * 案例4
         * */
        // const s1 = new MyPromise((resolve, reject) => {
        //     resolve(100);
        //     throw Error('错误报错')
        // })
        // s1.then().catch(err => {
        //     console.log('失败', err)
        // })
2-纯净版代码
        function MyPromise(executor) {
            this.state = 'pending';
            this.value = null;
            this.onFulfilledCallbacks = [];
            this.onRejectedCallbacks = [];
            this.resolve = (res) => {
                if (this.state === 'pending') {
                    setTimeout(() => {
                        this.state = 'fulfilled';
                        this.value = res;
                        this.onFulfilledCallbacks.forEach(callback => {
                            callback(this.value)
                        })
                    });
                }
            }
            this.reject = (err) => {
                if (this.state === 'pending') {
                    setTimeout(() => {
                        this.state = 'rejected';
                        this.value = err;
                        this.onRejectedCallbacks.forEach(callback => {
                            callback(this.value)
                        })
                    });
                }
            }

            try {
                executor(this.resolve, this.reject);
            } catch (e) {
                this.reject(e)
            }
        }

        MyPromise.prototype.catch = function (error) {
            return this.then(null, error);
        }

        MyPromise.prototype.then = function (res, err) {
            res = typeof res === 'function' ? res : value => value;
            err = typeof err === 'function' ? err : reason => {
                throw reason;
            };

            const promise = new MyPromise((resolve, reject) => {
                if (this.state === 'fulfilled') {
                    setTimeout(() => {
                        try {
                            let x = res(this.value);
                            this.resolvePromise(promise, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    });
                } else if (this.state === 'rejected') {
                    setTimeout(() => {
                        try {
                            let x = err(this.value);
                            this.resolvePromise(promise, x, resolve, reject);
                        } catch (e) {
                            reject(e)
                        }
                    });
                } else if (this.state === 'pending') {
                    this.onFulfilledCallbacks.push(() => {
                        setTimeout(() => {
                            try {
                                let x = res(this.value);
                                this.resolvePromise(promise, x, resolve, reject)
                            } catch (e) {
                                reject(e);
                            }
                        });
                    });
                    this.onRejectedCallbacks.push(() => {
                        setTimeout(() => {
                            try {
                                let x = err(this.value);
                                this.resolvePromise(promise, x, resolve, reject);
                            } catch (e) {
                                reject(e);
                            }
                        });
                    });
                }
            })
            return promise
        }
        MyPromise.prototype.resolvePromise = function (promise, x, resolve, reject) {
            if (x === promise) {
                return reject(new TypeError('Chaining cycle detected for promise'));
            }
            if (x instanceof MyPromise) {
                if (x.state === 'pending') {
                    x.then(y => {
                        this.resolvePromise(promise, y, resolve, reject)
                    }, reject);
                } else if (x.state === 'fulfilled') {
                    resolve(x.value);
                } else if (x.state === 'rejected') {
                    reject(x.value);
                }
            } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
                try {
                    var then = x.then;
                } catch (e) {
                    return reject(e);
                }
                if (typeof then === 'function') {
                    let called = false;
                    try {
                        then.call(
                            x,
                            y => {
                                if (called) return;
                                called = true;
                                this.resolvePromise(promise, y, resolve, reject);
                            },
                            r => {
                                if (called) return;
                                called = true;
                                reject(r);
                            }
                        )
                    } catch (e) {
                        if (called) return;
                        called = true;
                        reject(e);
                    }
                } else {
                    resolve(x);
                }
            } else {
                resolve(x);
            }
        }

        /**
         * 案例1
         * resolve 在延时一秒后才会改变 then会先执行
         * */
        // const s = new MyPromise((resolve, reject) => {
        //     setTimeout(() => {
        //         resolve('成功');
        //     }, 1000)
        // })
        // s.then(res => {
        //     console.log(res);//undefined
        // })

        /**
         * 案例2
         * */
        // const s = new MyPromise((resolve, reject) => {
        //     setTimeout(() => {
        //         resolve('成功');
        //     }, 1000)
        // })
        // const p = s.then(res => {
        //     return p;
        // })
        /**
         * 案例3
         * */
        // const s = new MyPromise((resolve, reject) => {
        //     resolve(100);
        //     throw Error('错误报错')
        // })
        // s.then(null, err => {
        //     console.log('失败', err)
        // })
        /**
         * 案例4
         * */
        // const s1 = new MyPromise((resolve, reject) => {
        //     resolve(100);
        //     throw Error('错误报错')
        // })
        // s1.then().catch(err => {
        //     console.log('失败', err)
        // })
3-测试实现结果

相信以上的代码和注释已经十分详细了,接下来跑一下官方的测试用例。
1-npm install promises-aplus-tests -D
2-根目录下新建一个MyPromise.js文件,把我们写好的代码直接丢进去,并在末尾进行配置并导出

MyPromise.deferred = function () {
    var result = {};
    result.promise = new MyPromise(function (resolve, reject) {
        result.resolve = resolve;
        result.reject = reject;
    });

    return result;
}

module.exports = MyPromise;

3-根目录下新建一个package.json文件,配置一下启动项

{
    "devDependencies": {
        "promises-aplus-tests": "^2.1.2"
    },
    "scripts": {
        "start": "promises-aplus-tests MyPromise"
    }
}

4-直接启动:npm run start
promise.png

完美兼容官方的测试用例,至此,码完收工!