2017年2月15日 星期三

(js)SimpleDeferred(核心採用jquery方法)

if (typeof module === 'object' && typeof module.exports === 'object') {
    module.exports = Deferred;
}
////////////////////////////////////////////////////////////////////////////////
/**
 * 2017/2/28
 * 定型版
 *
 * 內核以(jquery樣式)為主
 *
 */
////////////////////////////////////////////////////////////////////////////////


function Deferred() {
    if (!(this instanceof Deferred)) {
        return new Deferred();
    }
    var SimpleDeferred = Deferred.prototype.core;
    return new SimpleDeferred();
}
////////////////////////////////////////////////////////////////////////////////

/**
 * Deferred 登錄 class_SimpleDeferred
 */
(function(_self) {
    _self.prototype.core = SimpleDeferred;

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


    ////////////////////////////////////////////////////////////////////////////

    function SimpleDeferred() {

        this.parent = _self;
        this.Job = Job;
        this.Promise = Promise_;
        this.fn = this.constructor;
        /* ------------------------ */

        this.id = this.fn.id++; // for test
        this.value;
        this.reason;
        this.status = PENDING; // 需要做成 primative
        this.jobList = [];

    }

    SimpleDeferred.id = 0;
    ////////////////////////////////////////////////////////////////////////////
    (function() {

        /**
         * 註冊 job
         */
        this.prototype.then = function(onFulfilled, onRejected) {
            var res, self = this;

            /* ---------------------------------- */
            // check
            if (onFulfilled != null && typeof onFulfilled !== 'function') {
                throw new TypeError('then.args[0] must be function');
            }

            if (onRejected != null && typeof onRejected !== 'function') {
                throw new TypeError('then.args[1] must be function');
            }

            /* ---------------------------------- */

            if (this.status === PENDING) {
                // 若任務尚在執行

                var job = new this.Job(onFulfilled, onRejected);
                this.jobList.push(job);

            } else {

                if (this.status === FULFILLED) {
                    // 任務已經正常結束
                    (typeof onFulfilled === 'function') && (res = onFulfilled(this.value));

                } else {
                    // 任務已經異常結束
                    (typeof onRejected === 'function') && (res = onRejected(this.reason));
                }

                (res instanceof SimpleDeferred) && (self = res);

            }
            /* ---------------------------------- */
            return self;
        };


        /* ================================================================== */
        /**
         * 通知執行狀態
         *
         * @param {boolean} specialCondition: 是否是特殊情況
         * 通常已經執行過的的,不能再 resolve()
         * 但某些狀況會例外
         *
         */
        this.prototype.resolve = function(value) {

            this.value = value;
            /* ------------------------ */
            this.status = FULFILLED;
            /* ------------------------ */
            /**
             * 執行註冊過的 job
             */
            this._doJob();

            return this;
        };

        /* ================================================================== */
        /**
         * 通知執行狀態
         * @param {boolean} specialCondition: 是否是特殊情況
         * 通常已經執行過的的,不能再 reject()
         * 但某些狀況會例外
         */
        this.prototype.reject = function(reason) {
            /* ------------------------ */

            if (arguments.length > 1) {
                this.reason = [].slice.call(arguments);
            } else {
                this.reason = reason;
            }
            /* ------------------------ */
            this.status = REJECTED;
            /* ------------------------ */
            /**
             * 執行註冊過的 job
             */
            this._doJob();

            return this;
        };
        /* ================================================================== */
        this.prototype.done = function(onFulfilled) {
            var self = this.then(onFulfilled, undefined);
            return self;
        };
        /* ================================================================== */
        this.prototype.catch = function(onRejected) {
            var self = this.then(undefined, onRejected);
            return self;
        };
        /* ================================================================== */
        this.prototype.fail = function(onRejected) {
            var self = this.then(undefined, onRejected);
            return self;
        };

        this.prototype.promise = function() {
            var promise = new this.Promise(this);
            return promise;
        };

    }).call(SimpleDeferred);
    ////////////////////////////////////////////////////////////////////////////
    (function() {
        /**
         * 執行所有註冊的任務
         */
        this.prototype._doJob = function() {
            /**
             * 執行註冊過的 job
             */
            while (this.jobList.length) {
                // 表示有等待他完成後要執行的工作
                var callback, outValue;
                var job = this.jobList.shift();
                /* ---------------------------------- */

                if (this.status === FULFILLED) {
                    outValue = this.value;
                    callback = job.getResolve();
                } else {
                    outValue = this.reason;
                    callback = job.getReject();
                }
                /* ---------------------------------- */
                if (typeof callback !== 'function') {
                    continue;
                }
                /* ---------------------------------- */
                // 比較麻煩之處
                var res = callback(outValue);
                /* ---------------------------------- */
                if ((res instanceof SimpleDeferred) || (res instanceof this.Promise)) {
                    if (res instanceof this.Promise) {
                        res = res.def;
                    }

                    // 把責任交出去
                    res.jobList = res.jobList.concat(this.jobList);

                    this.jobList = [];

                    // 檢查 res 的狀態
                    switch (res.status) {
                        case PENDING:
                            /* do nothing,等待他完成事項 */
                            break;
                        case FULFILLED:
                            /**
                             * 因為 res 已經執行過 resolve
                             * 所以要把 resolve(value)再傳一遍
                             */
                            res.resolve(res.value);
                            break;
                        case REJECTED:
                            /**
                             * 因為 res 已經執行過 reject
                             * 所以要把 reject(reason)再傳一遍
                             */
                            res.reject(res.reason);
                            break;
                        default:
                            break;
                    }
                    break;
                } else {
                    continue;
                }
                /* ---------------------------------- */
            }
        };
    }).call(SimpleDeferred);
    ////////////////////////////////////////////////////////////////////////////
    /**
     * 處理 then()
     */
    function Job(onFulfilled, onRejected) {
        var self = this;

        var resolve;
        var reject;

        __construct();

        /**
         * 建構式
         */
        function __construct() {
            (typeof onFulfilled === 'function') && (resolve = onFulfilled);
            (typeof onRejected === 'function') && (reject = onRejected);
        }

        /* ================================================================== */
        this.getResolve = function() {
            return (typeof resolve === 'function' ? resolve : undefined);
        };
        /* ================================================================== */
        this.getReject = function() {
            return (typeof reject === 'function' ? reject : undefined);
        };
    }
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Deferred 的包裹
     */

    function Promise_(deferred) {
        this.def = deferred;
    }

    (function() {
        this.then = function(onFulfilled, onRejected) {
            this.def.then(onFulfilled, onRejected);
            return this;
        };

        this.done = function(onFulfilled) {
            this.def.done(onFulfilled);
            return this;
        };

        this.fail = function(onRejected) {
            this.def.fail(onRejected);
            return this;
        };

        this.catch = function(onRejected) {
            this.def.catch(onRejected);
            return this;
        };
    }).call(Promise_.prototype);

})(Deferred);
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////

(function(_self) {
    /**
     * Deferred 類別方法
     *
     * 只有全部任務完成才能 resolve
     * 其中之一拋出 reject 就全 out
     */
    _self.all = function(defList) {

        if (!Array.isArray(defList)) {
            throw new TypeError('arg must be array');
        }
        var all_def = Deferred();

        data = {
            self: all_def,
            values: [],
            reasons: undefined,
            count: defList.length
        };
        /* ---------------------------------- */
        // 為每個 deferred 註冊事件
        for (var i = 0; i < defList.length; i++) {
            data.values[i] = undefined;

            var _def = defList[i];
            bindEvent_1(_def, data, i);
        }
        return all_def;
    };


    function bindEvent_1(_def, data, index) {
        var self = data.self;

        _def.then(function(value) {
            data.values[index] = value;

            if (--data.count == 0) {
                // defList 都完成
                self.resolve(data.values);
            }
        }, function(reason) {

            var values = data.values.slice(0);
            self.reject(reason, values);
        });
    };
    /* ====================================================================== */

    /**
     * Deferred 類別方法
     */
    _self.race = function(defList) {
        if (!Array.isArray(defList)) {
            throw new TypeError('arg must be array');
        }

        var race_def = Deferred();

        data = {
            self: race_def,
            values: undefined,
            reasons: undefined,
            count: defList.length
        };
        /* ---------------------------------- */
        // 為每個 deferred 註冊事件
        for (var i = 0; i < defList.length; i++) {
            data.values[i] = undefined;

            var _def = defList[i];
            bindEvent_2(_def, data, i);
        }

        return race_def;
    };


    function bindEvent_2(_def, data, index) {
        var self = data.self;

        _def.then(function(value) {
            // defList 都完成
            self.resolve(values);
        }, function(reason) {
            self.reject(reason);
        });
    }
})(Deferred);
////////////////////////////////////////////////////////////////////////////////