2017年3月27日 星期一

(js)PDeferred(2017-3-27)


////////////////////////////////////////////////////////////////////////////////
/**
 * 2017/4/7
 *
 * promise, deferred 的擴充
 *
 * 以(promise)為核心
 *
 * 以 series 為主
 *
 * 若 series 中間有錯,錯誤點的後面就是錯誤
 * 與 promise 不同,promise 若錯誤在 catch 後會回到 resolve
 *
 * 這樣做是方便用在 tree
 */
////////////////////////////////////////////////////////////////////////////////

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

    if (typeof setImmediate !== 'undefined') {
        asyncFun = function(fn) {
            setImmediate(fn);
        }
    } else if (typeof process !== 'undefined' && process.nextTick) {
        asyncFun = function(fn) {
            process.nextTick(fn);
        }
    } else {
        asyncFun = function(fn) {
            setTimeout(fn, 0);
        }
    }
    ////////////////////////////////////////////////////////////////////////////

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

        if (!(this instanceof Promise_)) {
            if (name == null || typeof name === 'string') {
                return new Promise_(name);
            } else {
                // 轉換
                return conversion(name);
            }
        }
        /* ------------------------ */
        var self = this;
        /* ------------------------ */
        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;

        if (typeof name === 'function') {
            return conversion(name);
        } else {
            __construct(this);
        }

        /* ------------------------ */
        function __construct(promise) {
            if (typeof name === 'string' && name) {
                promise.name = name;
            }
        }
    }
    /* ---------------------------------------------------------------------- */
    // for test
    Promise_.id = 0;
    /**************************************************************************/
    (function(fn) {
        // value 可以是任何質,但不能是自己的實例
        // 但 value 若是 promise 得承接
        // 從此實現與其他庫的 promise 轉接
        this.resolve = function(value) {
            // debugger;

            var self = this;

            if (value === this) {
                // 不能 resolve(self)
                // 會變成無窮回圈
                throw TypeError("can't resolve(self)");
            }

            if (this.fired) {
                // 已執行過
                return;
            }
            /* ---------------------------------- */
            if (isThenable(value)) {
                // 若 resolve(promise)

                value.then(function(_value) {
                    self.resolve(_value);
                }, function(_reason) {
                    self.reject(_reason);
                });

                return;
            } else {
                // 一般情況
                this.status = FULFILLED;
                this.fired = true;
                this.value = value;

                this._doJob();
            }

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

            if (reason === this) {
                // 不能 resolve(self)
                // 會變成無窮回圈
                throw TypeError("can't reject(self)");
            }
            /* ---------------------------------- */

            if (this.fired) {
                // 已執行過
                return;
            }
            this.fired = true;
            this.status = REJECTED;


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

            this._doJob();

            return this;
        };
        /* ================================================================== */
        // 返回 promise
        // 給 return def.promise() 時用的
        // 直接返回 promise
        this.promise = function() {
            return Promise.resolve(this);
        }
    }).call(Promise_.prototype, Promise_);
    /**************************************************************************/
    (function(fn) {
        // 返回 promise
        this.pThen = function(onFulfilled, onRejected) {
            var def = this.then(onFulfilled, onRejected);
            return Promise.resolve(def);
        };

        /*
          整個程式最重要的地方
         */
        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 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 return_value, return_promise, next_promise = this.next;;

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

                // 若要符合 promise 規則啟用下面註解
                // promise 只會執行一次 catch(),皆下來的 catch()都不會執行
                // return;
            } else {
                // 若沒有設定(onRejected)
                // 正常程序
                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 {
                // 正常程序
                // 不使用 return_value
                // 直接傳輸最初的 error 訊息
                next_promise.reject(reason);
            }
        }
    }).call(Promise_.prototype, Promise_);

    /**************************************************************************/
    (function(fn) {
        // return promise
        this.pCatch = function(onRejected) {
            var def = this.catch(onRejected)
            return new Promise.resolve(def);
        };

        this.catch = function(onRejected) {
            var promise = this.then(undefined, onRejected);
            return promise;
        };
        /* ================================================================== */
        this.pDone = function(onFulfilled) {
            var def = this.done(onFulfilled);
            return new Promise.resolve(def);
        };

        this.done = function(onFulfilled) {
            var promise = this.then(onFulfilled, undefined);
            return promise;
        };
        /* ================================================================== */
        this.pAlways = function(callback) {
            var def = this.always(callback);
            return new Promise.resolve(def);
        };

        this.always = function(callback) {

            var promise = this.then(function(value) {
                return callback(undefined, value);
            }, function(reason) {

                // 怕 reject(undefined)
                return callback(true, reason);
            });
            return promise;
        };
        /* ================================================================== */
        this.pDelay = function() {
            var def = this.delay(time);
            return new Promise.resolve(def);
        };

        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;

            // 若要合乎 promise 的精神
            // 要把下列步驟包在 async 裡面
            // 確保 resolve, reject 區塊裡的程式執行完
            // 才會接著執行 then 註冊的函式
            //
            // 但若不用 async 也是沒有太大影響
            // 不用 async 可確保 resolve 後馬上準確的執行
            // 不用再等排程去執行 then 註冊的 fun



            // 若裡面 then 註冊的函式
            // 合乎排程
            // 而不是 resolve()後馬上調用
            asyncFun(function() {
                while (this.jobList.length) {
                    // 表示有等待他完成後要執行的工作
                    var callback;
                    var job = this.jobList.shift();
                    /* ---------------------------------- */

                    if (this.status === FULFILLED) {

                        callback = job.getResolve();
                    } else {

                        callback = job.getReject();
                    }
                    /* ---------------------------------- */
                    // 與 ypromise 不同
                    // 每個 then(function)都得進入排程

                    callback(this.value, this.reason);
                }
            }.bind(this));

        };

    }).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.pAll = function(promises) {
            var def = fn.all(promises);
            return new Promise.resolve(def);
        };

        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) {

                data.detail[i] = undefined;

                if (!isThenable(_promise)) {
                    return;
                }

                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,
                    reason: reason,
                    data: this.detail.slice()
                };

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

    (function(fn) {
        fn.pRace = function(promises) {
            var def = fn.race(promises);
            return new Promise.resolve(def);
        };

        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 (!isThenable(_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_);


    ////////////////////////////////////////////////////////////////////////////
    // tool function

    // 判斷是否屬於 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;
    };
    /* ---------------------------------------------------------------------- */

})(this || {});

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

(function(root) {

    if (typeof root.Promise !== 'function') {
        return;
    }

    if (typeof root.jQuery === 'function') {
        $.Pd_ = $.Pdeferred_ = PDeferred;
    } else {
        root.$Pd_ = root.$Pdeferred_ = PDeferred;
    }
    /* ====================================================================== */
    // 用預設 promise 當核心的 Deferred
    function PDeferred(fn) {

        if (!(this instanceof PDeferred)) {
            return new PDeferred(fn);
        }

        var self = this;
        this._resolve;
        this.reject;

        this.promise = new Promise(function(resolve, reject) {
            self._resolve = resolve;
            self._reject = reject;

            fn(self);
        });
    }
    /* ====================================================================== */
    (function() {
        this.resolve = function(value) {
            this._resolve(value);
        };
        /* ---------------------------------- */
        this.reject = function(reason) {
            this._reject(reason);
        };
        /* ---------------------------------- */
        this.promise = function() {
            return this.promise;
        }
    }).call(PDeferred.prototype);
})(this || {});