2017年2月13日 星期一

(js)promise小問題修正版

////////////////////////////////////////////////////////////////////////////////
/**
 * 2017/2/14
 *
 * fix from https://github.com/ygm125/promise
 */
////////////////////////////////////////////////////////////////////////////////

(function(exports) {

    if (typeof window === 'object') {
        // is window
        exports._Promise = Promise;
        try {
            new Promise();
            return;
        } catch (e) {
            exports.Promise = Promise;
        }

    } else if (typeof module === 'object' && module.exports) {
        // is node.js
        module.exports = Promise;
    } else {
        return;
    }
    ////////////////////////////////////////////////////////////////////////////

    var PENDING = undefined; // 事件仍在處理狀態
    var FULFILLED = 1; // 事件已處理完成
    var REJECTED = 2; // 事件已處理完,但拋出 reject

    /**
     * @param {string} name 測試用,命名以方便與 then() 生成的 promise做區分
     */
    function Promise(_resolver, name, parent) {
        // debugger;
        if (typeof _resolver !== 'function') {
            throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
        }

        if (!(this instanceof Promise)) {
            // 當作函式用的話
            return new Promise(_resolver, name);
        }
        /* ---------------------------------- */
        // for test 方便辨別 promise 身份
        this._parent = (parent instanceof Promise ? parent : undefined); // 紀錄上一個 promise
        this.guid = Promise.prototype.guid++; // for test
        this._name = (name ? (name + '(' + this.guid + ')') : this.guid); // for test
        /* ---------------------------------- */
        var promise = this;
        promise._value;
        promise._reason;
        promise._status = PENDING;
        /* ---------------------------------- */
        promise._resolves = []; // then([0])的工作都放這
        promise._rejects = []; // then([1])的工作都放這
        /* ---------------------------------- */
        try {
            // 處理 promise 程序拋出 error
            _resolver(resolve, reject);
        } catch (error) {
            reject(error);
        }
        /* ================================== */

        function resolve(value) {
            promise._status = FULFILLED;

            reportFinshJob.call(promise, value);
        }
        /* ---------------------------------- */
        function reject(reason) {
            promise._status = REJECTED;

            reportFinshJob.call(promise, reason);
        }
    };
    /* ====================================================================== */
    Promise.prototype.guid = 0; // for test
    ////////////////////////////////////////////////////////////////////////////
    /**
     * very important
     */
    Promise.prototype.then = function(onFulfilled, onRejected) {
        // debugger;
        var promise = this;

        // 每次返回一个promise,保证是可thenable的
        return new Promise(function(resolve, reject) {
            // debugger;

            if (promise._status === PENDING) {
                promise._resolves.push(callback);
                promise._rejects.push(errback);
            } else if (promise._status === FULFILLED) { // 状态改变后的then操作,立刻执行
                callback(promise._value);
            } else if (promise._status === REJECTED) {
                errback(promise._reason);
            }
            /* ============================================ */
            function callback(value) {
                var ret;
                try {
                    /**
                     * 修改此處
                     *
                     * 處理 then.onFulfilled() 萬一出錯
                     */
                    ret = (typeof onFulfilled === 'function' && onFulfilled(value));
                } catch (error) {
                    reject(error);
                }
                ret = ret || value;

                if (isThenable(ret)) {
                    ret.then(function(value) {
                        resolve(value);
                    }, function(reason) {
                        reject(reason);
                    });
                } else {
                    resolve(ret);
                }
            }
            /* ---------------------------------------- */
            function errback(reason) {
                var _reason;

                try {
                    _reason = (typeof onRejected === 'function' && onRejected(reason));
                } catch (error) {
                    _reason = error;
                } finally {
                    _reason = _reason || reason;
                    reject(_reason);
                }
            }
        }, 'then', promise);
    };
    /* ====================================================================== */
    Promise.prototype.catch = function(onRejected) {
        return this.then(undefined, onRejected)
    };
    /* ====================================================================== */
    Promise.prototype.delay = function(ms, val) {
        return this.then(function(ori) {
            return Promise.delay(ms, val || ori);
        })
    };
    /* ====================================================================== */
    Promise.delay = function(ms, val) {
        return Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve(val);
            }, ms);
        })
    };
    /* ====================================================================== */
    Promise.resolve = function(arg) {
        return Promise(function(resolve, reject) {
            resolve(arg)
        }, 'resolve');
    };
    /* ====================================================================== */
    Promise.reject = function(arg) {
        return Promise(function(resolve, reject) {
            reject(arg)
        }, 'reject');
    };
    /* ====================================================================== */
    Promise.all = function(promises) {
        if (!isArray(promises)) {
            throw new TypeError('You must pass an array to all.');
        }
        return Promise(function(resolve, reject) {
            var i = 0,
                result = [],
                len = promises.length,
                count = len

            function resolver(index) {
                return function(value) {
                    resolveAll(index, value);
                };
            }

            function rejecter(reason) {
                reject(reason);
            }

            function resolveAll(index, value) {
                result[index] = value;
                if (--count == 0) {
                    resolve(result)
                }
            }

            for (; i < len; i++) {
                promises[i].then(resolver(i), rejecter);
            }
        }, 'all');
    };
    /* ====================================================================== */
    Promise.race = function(promises) {
        if (!isArray(promises)) {
            throw new TypeError('You must pass an array to race.');
        }
        return Promise(function(resolve, reject) {
            var i = 0,
                len = promises.length;

            function resolver(value) {
                resolve(value);
            }

            function rejecter(reason) {
                reject(reason);
            }

            for (; i < len; i++) {
                promises[i].then(resolver, rejecter);
            }
        }, 'race');
    };

    ////////////////////////////////////////////////////////////////////////////
    function isArray(obj) {
        return Object.prototype.toString.call(obj) === "[object Array]";
    };
    /* ====================================================================== */
    /**
     * 若對象含有.then()
     */
    function isThenable(obj) {
        return (obj && typeof obj['then'] == 'function');
    };
    /* ====================================================================== */
    /**
     * promise.resolve() || promise.reject() 後要做的事
     */
    function reportFinshJob(value) {
        // debugger;
        var promise = this;

        publish.call(promise, value);
    };
    /* ====================================================================== */
    /**
     * 執行(._resolves)or(_rejects)
     */
    function publish(val) {

        var promise = this,
            fn,
            st = promise._status === FULFILLED;

        // 任務隊列
        var queue = promise[st ? '_resolves' : '_rejects'];

        while (fn = queue.shift()) {
            // 裡面是 callback || errback 包住下一個 promise
            val = fn.call(promise, val) || val;
        }
        promise[st ? '_value' : '_reason'] = val;

        promise._resolves = [];
        promise._rejects = [];
    }
})(this || {});