2017年2月28日 星期二

Deferred(以 promise 為核心)

/**
 * promise, deferred 的擴充
 */
(function($) {

    if (typeof $.jQuery === 'function') {
        /**
         * 若有 load jquery
         * $.Async(), new $.Async()
         */
        $ = $.jQuery;
        $.Async = Promise_;
    } else if(typeof module === 'object' && module.exports === 'object'){

        // for node.js
        module.exports = Promise_;
    }else {
        /**
         * 若沒有 load jquery, for window
         *
         * Deferred(), new Deferred()
         */
        $.Deferred = Promise_;
    }

    ////////////////////////////////////////////////////////////////////////////
    /**
     * 以(promise)為核心
     *
     */
    ////////////////////////////////////////////////////////////////////////////

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

        if (!(this instanceof Promise_)) {
            if (typeof name === 'function') {
                var res = (function() {
                    var promise = new Promise_();

                    name(promise);

                    return promise
                })();

                return res;
            } else {
                return new Promise_(name);
            }
        }
        /* ------------------------ */
        this.prev; // 上一個 promise
        this.next; // 下一個 promise
        /* ------------------------ */
        this.fn = this.constructor;
        // this.Job = this.fn.prototype.Job;

        this.name = name || 'init';
        this.id = this.fn.id++;
        /* ------------------------ */
        this.jobList = [];
        /* ------------------------ */
        this.value;
        this.reason;
        /* ------------------------ */
        this.status = PENDING;
        /* ================================================================== */
        this.resolve = function(value) {

            this.status = FULFILLED;

            // 刪除 reject(), resolve()
            // 避免再次更動狀況
            delete this.reject;
            delete this.resolve;

            this.value = value;

            this._doJob();

            return this;
        };
        /* ================================================================== */
        this.reject = function(reason) {

            this.status = REJECTED;

            // 刪除 reject(), resolve()
            // 避免再次更動狀況
            delete this.reject;
            delete this.resolve;


            if (arguments.length > 1) {
                this.reason = [].slice.call(arguments);
            } else {
                this.reason = reason;
            }

            this._doJob();

            return this;
        };
    }
    Promise_.id = 0;
    /* ====================================================================== */
    (function(fn) {
        /**
         * 整個程式最重要的地方
         *
         */
        this.then = function(onFulfilled, onRejected) {
            // debugger;

            var next_promise = this.next = new fn('then');
            var self = next_promise.prev = this;

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

            if (this.status === PENDING) {
                var _callBack = callBack.bind(this, onFulfilled);
                var _errorBack = errorBack.bind(this, onRejected);

                var job = new this.Job(_callBack, _errorBack);
                this.jobList.push(job);

            } else if (this.status === FULFILLED) {
                // 立馬執行
                callBack.call(this, onFulfilled);
            } else if (this.status === REJECTED) {
                // 立馬執行
                errorBack.call(this, onRejected);
            }
            /* ----------------------------- */

            return next_promise;
        };
        /* ================================================================== */
        /**
         * call by this
         *
         * 把 onFulfilled 包起來
         *
         * 呼叫下一個 promise
         */
        function callBack(onFulfilled) {
            // debugger;

            var return_value, return_promise, next_promise = this.next;

            if (typeof onFulfilled === 'function') {
                return_value = onFulfilled(this.value);
            } else {
                // 若沒有設定(onFulfilled)
                // 呼叫子 promise
                next_promise.resolve(this.value);
                return;
            }
            /* ---------------------------------- */
            // 若有設定(onFulfilled)

            if (return_value instanceof Promise_) {

                return_promise = return_value;

                // 等待 ret 執行完
                return_promise.then(function(_value) {
                    // debugger;

                    next_promise.resolve(_value);
                }, function(_reason) {
                    // debugger;

                    next_promise.reject(_reason);
                });
            } else {
                // 呼叫子 promise
                next_promise.resolve(return_value);
            }
        }
        /* ================================================================== */
        /**
         * call by this
         *
         * 把 onRejected 包起來
         *
         * 呼叫下一個 promise
         */
        function errorBack(onRejected) {
            // debugger;

            var return_value, return_promise, next_promise = this.next;;

            if (typeof onRejected === 'function') {
                return_value = onRejected(this.reason);

                // 若要符合 promise 規則啟用下面註解
                // promise 只會執行一次 catch(),皆下來的 catch()都不會執行
                // return;
            } else {
                // 若沒有設定(onRejected)
                // 正常程序
                next_promise.reject(this.reason);
                return;
            }
            /* ---------------------------------- */
            // 若有設定(onRejected)

            if (return_value instanceof Promise_) {

                return_promise = return_value;

                // 等待 return_promise 執行完
                return_promise.then(function(_value) {
                    // debugger;

                    next_promise.resolve(_value);
                }, function(_reason) {
                    // debugger;

                    next_promise.reject(_reason);
                });
            } else {
                // 正常程序
                next_promise.reject(return_value);
            }
        }
    }).call(Promise_.prototype, Promise_);
    ////////////////////////////////////////////////////////////////////////////
    (function(fn) {
        this.catch = function(onRejected) {
            var promise = this.then(undefined, onRejected);
            return promise;
        };
        /* ================================================================== */
        this.fail = function(onRejected) {
            var promise = this.then(undefined, onRejected);
            return promise;
        };
        /* ================================================================== */
        this.done = function(onFulfilled) {
            var promise = this.then(onFulfilled, undefined);
            return promise;
        };
        /* ================================================================== */
        this.always = function(callback) {
            var promise = this.then(callback, callback);
            return promise;
        };

    }).call(Promise_.prototype, Promise_);


    ////////////////////////////////////////////////////////////////////////////
    (function(fn) {

        /* ================================================================== */
        this._doJob = function() {
            // debugger;
            /**
             * 執行註冊過的 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();
                }
                /* ---------------------------------- */
                /**
                 * callback
                 */
                callback(outValue);
            }

        };
        /* ================================================================== */
    }).call(Promise_.prototype, Promise_);
    ////////////////////////////////////////////////////////////////////////////


    (function(fn) {
        this.Job = Job;
        /**
         * class
         * 處理 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);
            };
        }
    }).call(Promise_.prototype, Promise_);

    ////////////////////////////////////////////////////////////////////////////
    /**
     * 類別方法
     */
    (function(fn) {
        fn.resolve = function(value) {
            // debugger;

            var promise = new fn();

            promise.resolve(value);

            return promise;
        };
        /* ================================================================== */
        fn.reject = function(reason) {
            // debugger;

            var promise = new fn();

            promise.reject(reason);

            return promise;
        };

    }).call(Promise_.prototype, Promise_);
    ////////////////////////////////////////////////////////////////////////////
    /**
     * 類別方法
     */
    (function(fn) {
        fn.all = function(promises) {
            if (!Array.isArray(promises)) {
                throw new TypeError('You must pass an array to all.');
            }
            var promise = new fn('all');
            var jobLength = promises.length;

            var data = {
                detail: [],
                index: undefined,
                reason: undefined,
                promise: promise,
                jobLength: jobLength
            };
            /* ---------------------------------- */
            promises.forEach(function(_promise, i) {

                if (!(_promise instanceof Promise_)) {
                    throw new TypeError('[' + i + '] not instance of Promise_');
                }

                data.detail[i] = undefined;

                var all_ok = get_okFn.call(data, i);
                var all_error = get_errorFn.call(data, i);

                _promise.then(all_ok, all_error);
            });
            /* ---------------------------------- */

            return promise;
        };
        /* ================================================================== */
        function get_okFn(i) {
            return function(value) {
                this.detail[i] = value;

                if (--this.jobLength < 1) {
                    // finish
                    this.promise.resolve(this.detail.slice());
                }
            }.bind(this);
        };
        /* ================================================================== */
        function get_errorFn(i) {
            return function(reason) {

                var error_data = {
                    index: i,
                    detail: reason,
                    data: this.detail.slice()
                };

                this.promise.reject(error_data);
            }.bind(this);
        };
    }).call(Promise_.prototype, Promise_);
    ////////////////////////////////////////////////////////////////////////////
    /**
     * 類別方法
     */

    (function() {

        /* ================================================================== */
        this.race = function(promises) {
            if (!Array.isArray(promises)) {
                throw new TypeError('You must pass an array to all.');
            }
            var promise = new fn('race');
            /* ---------------------------------- */
            promises.forEach(function(_promise, i) {

                if (!(_promise instanceof Promise_)) {
                    throw new TypeError('[' + i + '] not instance of Promise_');
                }

                _promise.then(function(value) {
                    promise.resolve(value);
                }, function(reason) {
                    promise.reject(reason);
                });
            });
            /* ---------------------------------- */
            return promise;
        };
    }).call(Promise_.prototype, Promise_);


})(this);