Promise

146 阅读6分钟

Promise

(function (window) {
    /**
     * 将经常使用的字符串定义为常量
     */
    const PENDING = 'pending';
    const RESOLVED = 'resolved';
    const REJECTED = 'rejected';

    /**
     * MyPromise 构造函数
     * 1. Promise 是一个类,在执行这个类的时候会传入一个执行器函数(excutor),这个执行器会立即执行(同步执行)
     * 2. Promise 会有三种状态
     *      -Pending 等待
     *      -Fulfilled 完成
     *      -Rejected 失败
     *
     * 3. 状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;
     * 4. Promise 中使用 resolve 和 reject 两个函数来更改状态;
     * 5. then 方法内部做但事情就是状态判断
     *      -如果状态是成功,调用成功回调函数
     *      -如果状态是失败,调用失败回调函数
     *
     * @constructor
     */
    function MyPromise(excutor) {

        // 将当前promise对象保存起来
        const that = this;

        // 给promise对象指定一个状态,初始值为pending
        that.status = PENDING;
        // 给promise对象指定一个用于存储结果数据的属性
        that.data = undefined;
        // 给promise对象添加回调函数,每个元素结构:{onResolved() {}, onRejected() {}}
        that.callbacks = [];

        /**
         * 1. 如果反复调用reason,后面的不执行,因为promise状态只改变一次
         * 2. 将状态改为resolved
         * 3. 保存value数据
         * 4. 如果有待执行的callback函数,立即【异步】执行回调(添加到异步队列中)
         *      不一定有回调函数,因为可能先指定,可能后指定
         * @param value
         */
        function resolve(value){
            // 1. 如果反复调用reason,后面的不执行,因为promise状态只改变一次
            if (that.status !== PENDING){
                return;
            }

            // 2. 将状态改为resolved
            that.status = RESOLVED;
            // 3. 保存value数据
            that.data = value;
            // 4. 如果有待执行的callback函数,立即【异步】执行成功的回调(添加到异步队列中,这里使用setTimeout)
            if (that.callbacks.length > 0){
                // 遍历,【异步执行】所有成功的回调函数
                that.callbacks.forEach(callbackObj => {
                    setTimeout(() => {
                        callbackObj.onResolved(value);
                    },0)
                })
            }

        }

        function reject(reason){
            // 1. 如果反复调用reason,后面的不执行,因为promise状态只改变一次
            if (that.status !== PENDING){
                return;
            }

            // 2. 将状态改为resolved
            that.status = REJECTED;
            // 3. 保存value数据
            that.data = reason;
            // 4. 如果有待执行的callback函数,立即【异步】执行失败的回调(添加到异步队列中,这里使用setTimeout)
            if (that.callbacks.length > 0){
                // 遍历,异步执行所有成功的回调函数
                that.callbacks.forEach(callbackObj => {
                    setTimeout(() => {
                        callbackObj.onRejected(reason);
                    },0)
                })
            }
        }

        /**
         * 立即执行exctuor,传两个用于改变promise状态的函数(需要内部提前定义好,由外部调用)
         * 执行器中可能:
         *     . 调resolve -> promise成功
         *     . 调reject -> promise失败
         *     . 抛异常 -> promise失败
         */
        try {
            excutor(resolve, reject)
        }catch (error) {
            reject(error)
        }

    }

    /**
     * Promise 原型对象的 then(), 指定成功和失败的回调函数(可以提前指定 或 之后指定)
     *
     * then()函数:
     * 1. 返回一个新的promise对象
     *  【new MyPromise】
     *
     * 2. 判断当前promise状态
     *  【that.status】
     *      1). 当前promise状态是pending,将成功和失败的回调函数保存callbacks容器中缓存
     *      2). 如果promise当前是resolve状态,异步执行onResolve并改变return的promise状态
     *      3). 如果promise当前是reject状态,异步执行onRejected并改变return的promise状态
     *
     * 3. 返回的promise的结果由回调函数 onResolved/onRejected 【异步】执行的结果决定
     *  【handle() - result】
     *      1). 如果抛出异常, return的promise就会失败, reason为error
     *      2). 如果回调函数返回的不是promise, return的promise就会成功, value就是返回的值
     *      3). 如果回调函数返回的是promise, return的promise结果就是这个promise的结果
     *
     * 4. 指定回调函数默认值(必须是函数)
     *
     * @param onResolved 成功的回调
     * @param onRejected 失败的回调
     */
    MyPromise.prototype.then = function (onResolved, onRejected){

        // 向后传递成功的value
        onResolved = typeof onResolved === 'function' ? onResolved : value => value
        // 指定默认的失败回调(实现错误/异常传透的关键), 向后传递失败的reason
        onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}

        const that = this;

        // 1. 返回一个新的promise对象
        return new MyPromise((resolve, reject) => {

            /**
             * 调用指定的回调函数处理, 根据执行的结果, 改变return的promise的状态
             *
             * 3. 返回的promise的结果由回调函数 onResolved/onRejected 【异步】执行的结果决定:
             *      1). 如果抛出异常, return的promise就会失败, reason为error
             *      2). 如果回调函数返回的不是promise, return的promise就会成功, value就是返回的值
             *      3). 如果回调函数返回的是promise, return的promise结果就是这个promise的结果
             *
             * @param callback 回调函数 onResolved/onRejected
             */
            function handle(callback) {
                try {
                    // 回调函数执行结果
                    const result = callback(that.data);
                    // 3). 如果回调函数返回的是promise, return的promise结果就是这个promise的结果
                    if (result instanceof MyPromise){
                        /**
                         * .then()获得回调函数返回的结果
                         *  => 当result成功时,让return的promise也成功
                         *  => 当result失败时,让return的promise也失败
                         *        result.then(
                         *           value => resolve(value),
                         *           reason => reject(reason)
                         *        )
                         *  => 简洁版
                         *        result.then(resolve, reject);
                         *          分析:result.then() 获得回调函数返回的结果
                         *               指定resolve接收成功的值,reject接收失败的值
                         */

                        result.then(resolve, reject);

                    } else { // 2). 如果回调函数返回的不是promise, return的promise就会成功, value就是返回的值
                        resolve(result)
                    }
                }catch (error) {
                    // 1). 异步回调函数onResolved如果抛出异常, return的promise失败(调用当前promise的reject)
                    reject(error)
                }
            }

            /**
             * 2. 判断当前promise状态 【that.status】
             *      1). 当前promise状态是pending,将成功和失败的回调函数保存callbacks容器中缓存
             *      2). 如果promise当前是resolve状态,异步执行onResolve并改变return的promise状态
             *      3). 如果promise当前是reject状态,异步执行onRejected并改变return的promise状态
             */
            // 1). 当前状态是pending,将回调函数保存起来
            if (that.status === PENDING){
                that.callbacks.push({
                    onResolved(value){
                        handle(onResolved)
                    },
                    onRejected(reason){
                        handle(onRejected)
                    }
                })
            }else if (that.status === RESOLVED){ // 2). 如果当前是resolve状态,异步执行onResolve并改变return的promise状态
                // 0. 回调函数【异步】执行
                setTimeout(() => {
                    handle(onResolved)
                },0)

            } else {  // 3). 如果当前是reject状态,异步执行onRejected并改变return的promise状态
                setTimeout(() => {
                    handle(onRejected)
                },0)
            }
        })

    };

    /**
     * Promise 原型对象的 catch(), 指定失败的回调函数,返回一个新的promise对象
     * @param onRejected 失败的回调
     */
    MyPromise.prototype.catch = function (onRejected){
        return this.then(undefined, onRejected)
    };

    /**
     * 函数对象的 resolve 方法, 返回一个【成功/失败】的promise
     * 1. 判断传入的value值
     *      1). 不是promise
     *          返回一个成功值为value的promise
     *      2). 是promise
     *          根据promise的返回结果,返回成功/失败的promise
     *
     * @param value 接收成功的数据, 可以是一般的值, 也可以是Promise对象
     */
    MyPromise.resolve = function (value){
        return new MyPromise((resolve, reject) => {
            if (value instanceof MyPromise){
                value.then(resolve, reject)
            }else {
                resolve(value)
            }
        })
    };

    /**
     * 函数对象的 reject 方法, 返回一个【失败】值为reason的promise对象
     * @param reason 接收失败的数据
     */
    MyPromise.reject = function (reason){
        return new MyPromise((resolve, reject) => {
            reject(reason)
        })
    };

    /**
     * 函数对象的 all 方法, 返回一个新的promise, 当所有promise都成功才成功, 否则有一个失败就失败
     * @param promises 对象数组
     */
    MyPromise.all = function (promises){
        // 用来保存所有成功的value的数组,指定长度
        const values = new Array(promises.length);
        // 用来保存成功的promise的数量
        let resolveCount = 0;

        // 返回一个新的promise
        return new MyPromise((resolve, reject) =>{
            // 遍历,取每个promise的结果
            promises.forEach((p, index) => {
                /**
                 * 传入的数组中,一般情况都是promise对象,但也可以包含非promise数据
                 * 所以将p包装成promise对象,省去判断
                 */
                MyPromise.resolve(p).then(
                    value => {
                        // p成功,保存value
                        resolveCount++;
                        values[index] = value;

                        // 如果全部成功,将return的promise变为成功
                        if (resolveCount === promises.length){
                            resolve(values)
                        }
                    },
                    reason => { // 只要有一个失败了,return的promise就失败
                        reject(reason)
                    }
                )
            })
        })

    };

    /**
     * 函数对象的 race 方法, 返回一个新的promise, 其结果由【第一个完成】的promise结果决定
     * @param promises 对象数组
     */
    MyPromise.race = function (promises){
        // 返回一个promise
        return new MyPromise((resolve, reject) =>{
            // 遍历,取每个promise的结果
            promises.forEach((p, index) => {
                MyPromise.resolve(p).then(
                    value => {
                        resolve(value)
                    },
                    reason => {
                        reject(reason)
                    }
                )
            })
        })

    };

    /**
     * 返回一个promise对象,它在指定的时间后才确定结果
     * @param value
     * @param time 延时时间
     */
    MyPromise.resolveDelay = function(value, time){
        return new MyPromise((resolve, reject) => {
            setTimeout(() => {
                if (value instanceof MyPromise){
                    value.then(resolve, reject)
                }else {
                    resolve(value)
                }
            }, time)
        })
    };

    /**
     * 返回一个promise对象,它在指定的时间后才失败
     * @param reason
     * @param time 延时时间
     */
    MyPromise.rejectDelay = function(reason, time){
        return new MyPromise((resolve, reject) => {
            setTimeout(() => {
                reject(reason)
            }, time)
        })
    };


    // 向外暴露模块
    window.MyPromise = MyPromise
})(window);