2017年4月5日 星期三

(js)yahoo promise

https://github.com/yahoo/ypromise



(function(global) {
    if (typeof window === 'object') {
        // browser
        if (typeof global.Promise === 'function') {
            // 若瀏覽器有內建 promise 就做罷
            return;
        }
    }
    /* ====================================================================== */

    var built = (function() {

        var status_pending = 1; // 事件仍在處理狀態
        var status_accepted = 2; // 已經 resolve 但尚未 fulfilled
        var status_fulfilled = 3; // 事件已處理完成
        var status_rejected = 4; // 事件已處理完,但拋出 reject

        /**
         A promise represents a value that may not yet be available. Promises allow
         you to chain asynchronous operations, write synchronous looking code and
         handle errors throughout the process.
       
         This constructor takes a function as a parameter where you can insert the logic
         that fulfills or rejects this promise. The fulfillment value and the rejection
         reason can be any JavaScript value. It's encouraged that rejection reasons be
         error objects
       
         <pre><code>
         var fulfilled = new Promise(function (resolve) {
         resolve('I am a fulfilled promise');
         });
       
         var rejected = new Promise(function (resolve, reject) {
         reject(new Error('I am a rejected promise'));
         });
         </code></pre>
       
         @class Promise
         @constructor
         @param {Function} fn A function where to insert the logic that resolves this
         promise. Receives `resolve` and `reject` functions as parameters.
         This function is called synchronously.
         **/
        function Promise(fn) {
            if (!(this instanceof Promise)) {
                throw new TypeError(this + 'is not a promise');
            }
            if (typeof fn !== 'function') {
                throw new TypeError('Promise resolver ' + fn + ' is not a function');
            }

            var resolver = new Resolver();

            /**
             A reference to the resolver object that handles this promise
           
             @property _resolver
             @type Object
             @private
             */
            this._resolver = resolver;

            try {
                fn(function(value) {
                    resolver.resolve(value);
                }, function(reason) {
                    resolver.reject(reason);
                });
            } catch (e) {
                resolver.reject(e);
            }
        }
        /* ================================================================== */

        (function() {
            /**
             Schedule execution of a callback to either or both of "fulfill" and
             "reject" resolutions for this promise. The callbacks are wrapped in a new
             promise and that promise is returned.  This allows operation chaining ala
             `functionA().then(functionB).then(functionC)` where `functionA` returns
             a promise, and `functionB` and `functionC` _may_ return promises.
           
             Asynchronicity of the callbacks is guaranteed.
           
             @method then
             @param {Function} [callback] function to execute if the promise
             resolves successfully
             @param {Function} [errback] function to execute if the promise
             resolves unsuccessfully
             @return {Promise} A promise wrapping the resolution of either "resolve" or
             "reject" callback
             **/
            this.then = function(callback, errback) {
                // using this.constructor allows for customized promises to be
                // returned instead of plain ones
                var resolve, reject,
                    promise = new this.constructor(function(res, rej) {
                        resolve = res;
                        reject = rej;
                    });
                /* ---------------------------------- */
                var _callback = (typeof callback === 'function' ?
                    Promise._makeCallback(promise, resolve, reject, callback) : resolve);

                var _errorback = (typeof errback === 'function' ?
                    Promise._makeCallback(promise, resolve, reject, errback) : reject);

                this._resolver._addCallbacks(_callback, _errorback);

                return promise;
            };
            /* ============================================================== */
            /*
             A shorthand for `promise.then(undefined, callback)`.
           
             Returns a new promise and the error callback gets the same treatment as in
             `then`: errors get caught and turned into rejections, and the return value
             of the callback becomes the fulfilled value of the returned promise.
           
             @method catch
             @param [Function] errback Callback to be called in case this promise is
             rejected
             @return {Promise} A new promise modified by the behavior of the error
             callback
             */
            this.catch = function(errback) {
                return this.then(undefined, errback);
            }
        }).call(Promise.prototype);
        ////////////////////////////////////////////////////////////////////////
        // 類別方法
        /**
         Wraps the callback in another function to catch exceptions and turn them
         into rejections.
       
         @method _makeCallback
         @param {Promise} promise Promise that will be affected by this callback
         @param {Function} fn Callback to wrap
         @return {Function}
         @static
         @private
         **/
        Promise._makeCallback = function(promise, resolve, reject, fn) {
            // callbacks and errbacks only get one argument
            return function(valueOrReason) {
                var result;

                // Promises model exception handling through callbacks
                // making both synchronous and asynchronous errors behave
                // the same way
                try {
                    // Use the argument coming in to the callback/errback from the
                    // resolution of the parent promise.
                    // The function must be called as a normal function, with no
                    // special value for |this|, as per Promises A+
                    // 執行 then() 註冊的函式
                    result = fn(valueOrReason);
                } catch (e) {
                    // calling return only to stop here
                    reject(e);
                    return;
                }

                if (result === promise) {
                    reject(new TypeError('Cannot resolve a promise with itself'));
                    return;
                }

                // 叫下一個 promise resolve()
                // resolve 包著下一個 promise 的 resolve()          
                resolve(result);
            };
        };
        /* ================================================================== */
        /*
         Ensures that a certain value is a promise. If it is not a promise, it wraps it
         in one.
       
         This method can be copied or inherited in subclasses. In that case it will
         check that the value passed to it is an instance of the correct class.
         This means that `PromiseSubclass.resolve()` will always return instances of
         `PromiseSubclass`.
       
         @method resolve
         @param {Any} Any object that may or may not be a promise
         @return {Promise}
         @static
         */
        Promise.resolve = function(value) {
            if (value && value.constructor === this) {
                return value;
            }
            /*jshint newcap: false */
            return new this(function(resolve) {
                /*jshint newcap: true */
                resolve(value);
            });
        };
        /* ================================================================== */
        /*
         A shorthand for creating a rejected promise.
       
         @method reject
         @param {Any} reason Reason for the rejection of this promise. Usually an Error
         Object
         @return {Promise} A rejected promise
         @static
         */
        Promise.reject = function(reason) {
            /*jshint newcap: false */
            var promise = new this(function() {});
            /*jshint newcap: true */

            // Do not go through resolver.reject() because an immediately rejected promise
            // always has no callbacks which would trigger an unnecessary warning
            promise._resolver._result = reason;
            promise._resolver._status = 'rejected';

            return promise;
        };
        /* =================================================================== */
        /*
         Returns a promise that is resolved or rejected when all values are resolved or
         any is rejected. This is useful for waiting for the resolution of multiple
         promises, such as reading multiple files in Node.js or making multiple XHR
         requests in the browser.
       
         @method all
         @param {Any[]} values An array of any kind of values, promises or not. If a value is not
         @return [Promise] A promise for an array of all the fulfillment values
         @static
         */
        Promise.all = function(values) {
            var Promise = this;
            return new Promise(function(resolve, reject) {
                if (!Array.isArray(values)) {
                    reject(new TypeError('Promise.all expects an array of values or promises'));
                    return;
                }

                var remaining = values.length,
                    i = 0,
                    length = values.length,
                    results = [];

                function oneDone(index) {
                    return function(value) {
                        results[index] = value;

                        remaining--;

                        if (!remaining) {
                            resolve(results);
                        }
                    };
                }

                if (length < 1) {
                    return resolve(results);
                }

                for (; i < length; i++) {
                    Promise.resolve(values[i]).then(oneDone(i), reject);
                }
            });
        };
        /* ================================================================== */
        /*
         Returns a promise that is resolved or rejected when any of values is either
         resolved or rejected. Can be used for providing early feedback in the UI
         while other operations are still pending.
       
         @method race
         @param {Any[]} values An array of values or promises
         @return {Promise}
         @static
         */
        Promise.race = function(values) {
            var Promise = this;
            return new Promise(function(resolve, reject) {
                if (!Array.isArray(values)) {
                    reject(new TypeError('Promise.race expects an array of values or promises'));
                    return;
                }

                // just go through the list and resolve and reject at the first change
                // This abuses the fact that calling resolve/reject multiple times
                // doesn't change the state of the returned promise
                for (var i = 0, count = values.length; i < count; i++) {
                    Promise.resolve(values[i]).then(resolve, reject);
                }
            });
        };
        /* ================================================================== */
        /**
         Forces a function to be run asynchronously, but as fast as possible. In Node.js
         this is achieved using `setImmediate` or `process.nextTick`. In YUI this is
         replaced with `Y.soon`.
       
         @method async
         @param {Function} callback The function to call asynchronously
         @static
         **/
        /* istanbul ignore next */
        Promise.async = typeof setImmediate !== 'undefined' ?
            function(fn) {
                setImmediate(fn);
            } :
            typeof process !== 'undefined' && process.nextTick ?
            process.nextTick :
            function(fn) {
                setTimeout(fn, 0);
            };

        ////////////////////////////////////////////////////////////////////////
        /**
         * 核心方法
         */

        /**
         Represents an asynchronous operation. Provides a
         standard API for subscribing to the moment that the operation completes either
         successfully (`fulfill()`) or unsuccessfully (`reject()`).
         @class Promise.Resolver
         @constructor
         **/
        function Resolver() {
            /**
             List of success callbacks
           
             @property _callbacks
             @type Array
             @private
             **/
            this._callbacks = [];

            /**
             List of failure callbacks
           
             @property _errbacks
             @type Array
             @private
             **/
            this._errbacks = [];

            /**
             The status of the operation. This property may take only one of the following
             values: 'pending', 'fulfilled' or 'rejected'.
           
             @property _status
             @type String
             @default 'pending'
             @private
             **/
            this._status = status_pending;

            /**
             This value that this promise represents.
           
             @property _result
             @type Any
             @private
             **/
            this._result = null;
        }
        /* ================================================================== */
        (function() {
            /**
             Resolves the promise, signaling successful completion of the
             represented operation. All "onFulfilled" subscriptions are executed and passed
             the value provided to this method. After calling `fulfill()`, `reject()` and
             `notify()` are disabled.
           
             @method fulfill
             @param {Any} value Value to pass along to the "onFulfilled" subscribers
             **/
            this.fulfill = function(value) {
                var status = this._status;

                if (status === status_pending || status === status_accepted) {
                    this._result = value;
                    this._status = status_fulfilled;
                }

                if (this._status === status_fulfilled) {
                    this._notify(this._callbacks, this._result);

                    // Reset the callback list so that future calls to fulfill()
                    // won't call the same callbacks again. Promises keep a list
                    // of callbacks, they're not the same as events. In practice,
                    // calls to fulfill() after the first one should not be made by
                    // the user but by then()
                    this._callbacks = [];

                    // Once a promise gets fulfilled it can't be rejected, so
                    // there is no point in keeping the list. Remove it to help
                    // garbage collection
                    this._errbacks = null;
                }
            };
            /* -------------------------------------------------------------- */

            /**
             Resolves the promise, signaling *un*successful completion of the
             represented operation. All "onRejected" subscriptions are executed with
             the value provided to this method. After calling `reject()`, `resolve()`
             and `notify()` are disabled.
           
             @method reject
             @param {Any} reason Value to pass along to the "reject" subscribers
             **/
            this.reject = function(reason) {
                var status = this._status;

                if (status === status_pending || status === status_accepted) {
                    this._result = reason;
                    this._status = status_rejected;
                }

                if (this._status === status_rejected) {
                    this._notify(this._errbacks, this._result);

                    // See fulfill()
                    this._callbacks = null;
                    this._errbacks = [];
                }
            };
            /* -------------------------------------------------------------- */

            /*
             Given a certain value A passed as a parameter, this method resolves the
             promise to the value A.
           
             If A is a promise, `resolve` will cause the resolver to adopt the state of A
             and once A is resolved, it will resolve the resolver's promise as well.
             This behavior "flattens" A by calling `then` recursively and essentially
             disallows promises-for-promises.
           
             This is the default algorithm used when using the function passed as the
             first argument to the promise initialization function. This means that
             the following code returns a promise for the value 'hello world':
           
             var promise1 = new Promise(function (resolve) {
             resolve('hello world');
             });
             var promise2 = new Promise(function (resolve) {
             resolve(promise1);
             });
             promise2.then(function (value) {
             assert(value === 'hello world'); // true
             });
           
             @method resolve
             @param [Any] value A regular JS value or a promise
             */
            this.resolve = function(value) {
                if (this._status === status_pending) {
                    this._status = status_accepted;
                    this._value = value;

                    if ((this._callbacks && this._callbacks.length) ||
                        (this._errbacks && this._errbacks.length)) {
                        this._unwrap(this._value);
                    }
                }
            };
            /* -------------------------------------------------------------- */
            /**
             If `value` is a promise or a thenable, it will be unwrapped by
             recursively calling its `then` method. If not, the resolver will be
             fulfilled with `value`.
           
             This method is called when the promise's `then` method is called and
             not in `resolve` to allow for lazy promises to be accepted and not
             resolved immediately.
           
             @method _unwrap
             @param {Any} value A promise, thenable or regular value
             @private
             **/
            this._unwrap = function(value) {
                var self = this,
                    unwrapped = false,
                    then;

                if (!value || (typeof value !== 'object' &&
                        typeof value !== 'function')) {
                    self.fulfill(value);
                    return;
                }

                try {
                    then = value.then;

                    if (typeof then === 'function') {
                        then.call(value, function(value) {
                            if (!unwrapped) {
                                unwrapped = true;
                                self._unwrap(value);
                            }
                        }, function(reason) {
                            if (!unwrapped) {
                                unwrapped = true;
                                self.reject(reason);
                            }
                        });
                    } else {
                        self.fulfill(value);
                    }
                } catch (e) {
                    if (!unwrapped) {
                        self.reject(e);
                    }
                }
            };
            /* -------------------------------------------------------------- */

            /**
             Schedule execution of a callback to either or both of "resolve" and
             "reject" resolutions of this resolver. If the resolver is not pending,
             the correct callback gets called automatically.
           
             @method _addCallbacks
             @param {Function} [callback] function to execute if the Resolver
             resolves successfully
             @param {Function} [errback] function to execute if the Resolver
             resolves unsuccessfully
             **/
            this._addCallbacks = function(callback, errback) {
                var callbackList = this._callbacks,
                    errbackList = this._errbacks;

                // Because the callback and errback are represented by a Resolver, it
                // must be fulfilled or rejected to propagate through the then() chain.
                // The same logic applies to resolve() and reject() for fulfillment.
                if (callbackList) {
                    callbackList.push(callback);
                }
                if (errbackList) {
                    errbackList.push(errback);
                }

                switch (this._status) {
                    case status_accepted:
                        this._unwrap(this._value);
                        break;
                    case status_fulfilled:
                        // 何時會進來??
                        // 迷樣的步驟
                        // 也許是應付 then()裡面發生錯誤
                        console.log('<<switch -> fulfilled>>');
                        this.fulfill(this._result);
                        break;
                    case status_rejected:
                        // 何時會進來??
                        // 迷樣的步驟
                        // 也許是應付 then()裡面發生錯誤
                        console.log('<<switch -> rejected>>');
                        this.reject(this._result);
                        break;
                }
            };
            /* -------------------------------------------------------------- */

            /**
             Executes an array of callbacks from a specified context, passing a set of
             arguments.
           
             @method _notify
             @param {Function[]} subs The array of subscriber callbacks
             @param {Any} result Value to pass the callbacks
             @protected
             **/
            this._notify = function(subs, result) {
                // Since callback lists are reset synchronously, the subs list never
                // changes after _notify() receives it. Avoid calling Y.soon() for
                // an empty list
                if (subs.length) {
                    // Calling all callbacks after Promise.async to guarantee
                    // asynchronicity. Because setTimeout can cause unnecessary
                    // delays that *can* become noticeable in some situations
                    // (especially in Node.js)
                    Promise.async(function() {
                        var i, len;

                        for (i = 0, len = subs.length; i < len; ++i) {
                            subs[i](result);
                        }
                    });
                }
            };
        }).call(Resolver.prototype);

        Promise.Resolver = Resolver;

        return Promise;

    })();
    /* ====================================================================== */

    if (typeof window === 'object') {
        window.Promise = built;
    } else if (typeof module === 'object' && typeof module.exports === 'object') {
        module.exports = built;
    }
})(this);