2017年8月11日 星期五

Promise 擴增,與 簡單的 $Deferred


(function () {

    if (typeof Promise !== 'function') {
        throw new Error('need Promise');
    }
    /*------------------------------------------------------------------------*/
    // promise.done()
    if (typeof Promise.prototype.done === 'undefined') {
        Promise.prototype.done = function (onFulfilled) {
            return this.then(onFulfilled, undefined);
        };
    }

    // promise.doneWith()
    if (typeof Promise.prototype.doneWith === 'undefined') {
        Promise.prototype.doneWith = function (onFulfilled, context) {
            onFulfilled = onFulfilled.bind(context);
            return this.then(onFulfilled, undefined);
        };
    }
    /*------------------------------------------------------------------------*/
    // promise.catchWith()
    if (typeof Promise.prototype.catchWith === 'undefined') {
        Promise.prototype.catchWith = function (onRejected, context) {
            onRejected = onRejected.bind(context);
            return this.then(undefined, onRejected);
        };
    }
    /*------------------------------------------------------------------------*/
    // promise.always()
    if (typeof Promise.prototype.always === 'undefined') {
        Promise.prototype.always = function (callback) {
            return this.then(function (data) {
                callback(false, data);
            }, function (error) {
                callback(true, error);
            });
        };
    }

    // promise.alwaysWith()
    if (typeof Promise.prototype.alwaysWith === 'undefined') {
        Promise.prototype.alwaysWith = function (callback, context) {

            callback = callback.bind(this);

            return this.then(function (data) {
                callback(false, data);
            }, function (error) {
                callback(true, error);
            });
        };
    }
    /*------------------------------------------------------------------------*/
    // setImmediate => process => setTimeout
    var async = typeof setImmediate !== 'undefined' ?
        (function (fn) {
            setImmediate(fn);
        }) : (typeof process !== 'undefined' && process.nextTick ?
            process.nextTick :
            function (fn) {
                setTimeout(fn, 0);
            });

    /*------------------------------------------------------------------------*/
    if (typeof module === 'object' && typeof module.exports === 'object') {
        module.exports = $Deferred;
    } else if (typeof window === 'object') {
        window.$Deferred = window.$Deferred || $Deferred;
    }
    /*------------------------------------------------------------------------*/
    function $Deferred() {
        if (this === window) {
            return new $Deferred();
        }
        var self = this;
        this._reject;
        this._resolve;
        this._promise = new Promise(function (resolve, reject) {
            self._resolve = resolve;
            self._reject = reject;
        });
    }

    (function () {
        this.p = this.promise = function () {
            return this._promise;
        };
        /**********************************************************************/
        this.resolve = function (arg, nextTick) {

            var self = this;

            if (nextTick != null && nextTick === true) {

                var job = (function () {
                    // 避免某些情況有些瀏覽器會叫
                    this._resolve(arg);
                }).bind(this);

                async(job);
            } else {
                this._resolve(arg);
            }
        };
        /**********************************************************************/
        this.reject = function (arg, nextTick) {
            var self = this;
            if (nextTick != null && nextTick === true) {

                var job = (function () {
                    // 避免某些情況有些瀏覽器會叫
                    this._reject(arg);
                }).bind(this);

                async(job);
            } else {
                this._reject(arg);
            }
        };
        /**********************************************************************/
        this.then = function (onFulfilled, onRejected) {
            var def = $Deferred();
            var p = this.promise();

            p = p.then(function (data) {
                return onFulfilled(data);
            }, function (error) {
                return onRejected(error);
            });
            /*--------------------------*/
            p.then(function (data) {
                def.resolve(data);
            }, function (error) {
                def.reject(error);
            });
            return def;
        };

        this.thenWith = function (onFulfilled, onRejected, context) {
            onFulfilled = onFulfilled.bind(context);
            onRejected = onRejected.bind(context);

            var def = $Deferred();
            var p = this.promise();

            p = p.then(function (data) {
                return onFulfilled(data);
            }, function (error) {
                return onRejected(error);
            });
            /*--------------------------*/
            p.then(function (data) {
                def.resolve(data);
            }, function (error) {
                def.reject(error);
            });
            return def;
        };
        /**********************************************************************/
        this.done = function (onFulfilled) {
            var def = $Deferred();
            var p = this.promise();


            p = p.then(function (data) {
                return onFulfilled(data);
            }, undefined);
            /*--------------------------*/
            p.then(function (data) {
                def.resolve(data);
            }, function (error) {
                def.reject(error);
            });
            return def;
        };

        this.doneWith = function (onFulfilled, context) {
            onFulfilled = onFulfilled.bind(context);

            var def = $Deferred();
            var p = this.promise();

            p = p.then(function (data) {
                return onFulfilled(data);
            }, undefined);
            /*--------------------------*/
            p.then(function (data) {
                def.resolve(data);
            }, function (error) {
                def.reject(error);
            });
            return def;
        };
        /**********************************************************************/
        this.catch = function (onRejected) {
            var def = $Deferred();
            var p = this.promise();

            p = p.catch(function (error) {
                return onRejected(error);
            });
            /*--------------------------*/
            p.then(function (data) {
                def.resolve(data);
            }, function (error) {
                def.reject(error);
            });
            return def;
        };

        this.catchWith = function (onRejected, context) {
            onRejected = onRejected.bind(context);

            var def = $Deferred();
            var p = this.promise();

            p = p.catch(function (error) {
                return onRejected(error);
            });
            /*--------------------------*/
            p.then(function (data) {
                def.resolve(data);
            }, function (error) {
                def.reject(error);
            });
            return def;
        };
        /**********************************************************************/
        this.always = function (callback) {
            var def = $Deferred();
            var p = this.promise();

            p = p.then(function (data) {
                return callback(false, data);
            }, function (error) {
                return callback(true, error);
            });
            /*--------------------------*/
            p.then(function (data) {
                def.resolve(data);
            }, function (error) {
                def.reject(error);
            });
            return def;
        };

        this.alwaysWith = function (callback, context) {
            callback = callback.binf(context);

            var def = $Deferred();
            var p = this.promise();

            p = p.then(function (data) {
                return callback(false, data);
            }, function (error) {
                return callback(true, error);
            });
            /*--------------------------*/
            p.then(function (data) {
                def.resolve(data);
            }, function (error) {
                def.reject(error);
            });
            return def;
        };
    }).call($Deferred.prototype);
})();