2017年4月1日 星期六

(js)Deferred => 遵照 promise 規則版,但不處理 try catch



////////////////////////////////////////////////////////////////////////////////
/**
 * 2017/4/1
 *
 * promise, deferred 的擴充
 *
 * 以(promise)為核心
 *
 * 盡量遵守 promise 規則
 *
 *
 */
////////////////////////////////////////////////////////////////////////////////

!(function(root) {
    if (typeof root.jQuery === 'function') {
        // jquery
        $.PDeferred_ = Promise_;
    } else if (typeof module === 'object' && typeof module.exports === 'object') {
        // for node.js
        module.exports = Promise_;
    } else if (typeof window === 'object') {
        root.$PDeferred_ = Promise_;
    } else {
        return;
    }
    ////////////////////////////////////////////////////////////////////////////

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

        if (!(this instanceof Promise_)) {
            if (typeof name === 'string') {
                return new Promise_(name);
            } else {
                // 轉換
                return conversion(name);
            }
        }
        /* ------------------------ */
        this.prev; // 上一個 promise
        this.next; // 下一個 promise
        /* ------------------------ */
        this.fn = this.constructor;
        // this.Job = this.fn.prototype.Job;

        this.name = 'init';
        this.id = this.fn.id++;
        /* ------------------------ */
        this.jobList = [];
        /* ------------------------ */
        this.fired = false; // 是否已執行過
        this.value;
        this.reason;
        /* ------------------------ */
        this.status = PENDING;

    }


    Promise_.id = 0;
    /* ====================================================================== */
    (function(fn) {
        this.resolve = function(value) {

            this.status = FULFILLED;
            /* ---------------------------------- */
            if (this.fired) {
                // 已執行過
                return;
            } else {
                this.fired = true;
            }
            /* ---------------------------------- */
            this.value = value;

            this._doJob();

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

            this.status = REJECTED;
            /* ---------------------------------- */
            if (this.fired) {
                // 已執行過
                return;
            } else {
                this.fired = true;
            }
            /* ---------------------------------- */
            if (arguments.length > 1) {
                this.reason = [].slice.call(arguments);
            } else {
                this.reason = reason;
            }

            this._doJob();

            return this;
        };
    }).call(Promise_.prototype, Promise_);
    /* ====================================================================== */
    (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, this.value, this.reason);
            } else if (this.status === REJECTED) {
                // 立馬執行
                errorBack.call(this, onRejected, this.value, this.reason);
            }
            /* ----------------------------- */
            return next_promise;
        };
        /* ================================================================== */
        /**
         * call by this
         *
         * 把 onFulfilled 包起來
         *
         * 呼叫下一個 promise
         */
        function callBack(onFulfilled, value, reason) {
            // debugger;
            var args = arguments;

            var return_value, return_promise, next_promise = this.next;

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

            if (isThenable(return_value)) {
                return_promise = return_value;

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

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

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

            var args = arguments;

            var return_value, return_promise, next_promise = this.next;

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

                // 若要符合 promise 規則啟用下面註解
                // promise 只會執行一次 catch(),皆下來的 catch()都不會執行

            } else {
                // promise 的走法
                // 還沒處理過 catch,所以繼續傳播 reject

                next_promise.reject(reason);
                return;
            }
            /* ---------------------------------- */
            // 若有設定(onRejected)        

            if (isThenable(return_value)) {
                // 正常程序應該是啟用下面
                // next_promise.reject(this.reason);

                // 但保留這個步驟
                // 可以讓發生的錯誤序列轉正(雖不合序列邏輯)

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

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

                    next_promise.reject(_reason);
                });
            } else {
                // promise 的走法
                // 已經處理過 catch 改走 resolve
                next_promise.resolve(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(function(value) {
                return callback(undefined, value);
            }, function(reason) {

                return callback({
                                       reason: reason
                                        });
            });
            return promise;
        };
        /* ================================================================== */
        this.delay = function(time) {

            var promise = this.then(function(value) {
                var def = Promise_();

                setTimeout(function() {
                    def.resolve(value);
                }, time);

                return def;
            }, function(reason) {
                var def = Promise_();

                setTimeout(function() {
                    def.reject(reason);
                }, time);

                return def;
            });

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


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

        /* ================================================================== */
        this._doJob = function() {
            // debugger;
            /**
             * 執行註冊過的 job
             */
            while (this.jobList.length) {
                // 表示有等待他完成後要執行的工作
                var callback;
                var job = this.jobList.shift();
                /* ---------------------------------- */

                if (this.status === FULFILLED) {

                    callback = job.getResolve();
                } else {

                    callback = job.getReject();
                }
                /* ---------------------------------- */
                /**
                 * callback
                 */
                callback(this.value, this._reason);
            }
        };

    }).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(fn) {

        fn.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_);


    ////////////////////////////////////////////////////////////////////////////
    // 判斷是否屬於 promise 之家族
    function isThenable(obj) {
        return obj && (typeof obj['then'] == 'function');
    };
    /* ====================================================================== */
    // 轉換
    function conversion(obj) {
        var res;

        if (typeof obj === 'function') {
            var _fn = obj;
            res = (function() {
                var promise = new Promise_();

                // 執行任務
                _fn(promise);

                return promise
            })();
            return res;

        } else if (isThenable(obj)) {
            // 轉接其他的 promise
            res = new Promise_();

            obj.then(function(value) {
                res.resolve(value);
            }, function(reason) {
                res.reject(reason);
            });

            return res;
        }
    };
})(this || {});

沒有留言:

張貼留言