    /* Ext.Deferred adapted from: [DeftJS](https://github.com/deftjs/deftjs5) Copyright (c) 2012-2013 [DeftJS Framework Contributors](http://deftjs.org) Open source under the [MIT License](http://en.wikipedia.org/wiki/MIT_License).  when(), all(), any(), some(), map(), reduce(), delay() and timeout() sequence(), parallel(), pipeline() methods adapted from: [when.js](https://github.com/cujojs/when) Copyright (c) B Cavalier & J Hann Open source under the [MIT License](http://en.wikipedia.org/wiki/MIT_License). */ /** * Deferreds are the mechanism used to create new Promises. A Deferred has a single * associated Promise that can be safely returned to external consumers to ensure they do * not interfere with the resolution or rejection of the deferred operation. * * This implementation of Promises is an extension of the ECMAScript 6 Promises API as * detailed [here][1]. For a compatible, though less full featured, API see `{@link Ext.Promise}`. * * A Deferred is typically used within the body of a function that performs an asynchronous * operation. When that operation succeeds, the Deferred should be resolved; if that * operation fails, the Deferred should be rejected. * * Each Deferred has an associated Promise. A Promise delegates `then` calls to its * Deferred's `then` method. In this way, access to Deferred operations are divided between * producer (Deferred) and consumer (Promise) roles. * * ## Basic Usage * * In it's most common form, a method will create and return a Promise like this: * *      // A method in a service class which uses a Store and returns a Promise *      // *      loadCompanies: function () { *          var deferred = new Ext.Deferred(); // create the Ext.Deferred object * *          this.companyStore.load({ *              callback: function (records, operation, success) { *                  if (success) { *                      // Use "deferred" to drive the promise: *                      deferred.resolve(records); *                  } *                  else { *                      // Use "deferred" to drive the promise: *                      deferred.reject("Error loading Companies."); *                  } *              } *          }); * *          return deferred.promise;  // return the Promise to the caller *      } * * You can see this method first creates a `{@link Ext.Deferred Deferred}` object. It then * returns its `Promise` object for use by the caller. Finally, in the asynchronous * callback, it resolves the `deferred` object if the call was successful, and rejects the * `deferred` if the call failed. * * When a Deferred's `resolve` method is called, it fulfills with the optionally specified * value. If `resolve` is called with a then-able (i.e.a Function or Object with a `then` * function, such as another Promise) it assimilates the then-able's result; the Deferred * provides its own `resolve` and `reject` methods as the onFulfilled or onRejected * arguments in a call to that then-able's `then` function. If an error is thrown while * calling the then-able's `then` function (prior to any call back to the specified * `resolve` or `reject` methods), the Deferred rejects with that error. If a Deferred's * `resolve` method is called with its own Promise, it rejects with a TypeError. * * When a Deferred's `reject` method is called, it rejects with the optionally specified * reason. * * Each time a Deferred's `then` method is called, it captures a pair of optional * onFulfilled and onRejected callbacks and returns a Promise of the Deferred's future * value as transformed by those callbacks. * * See `{@link Ext.promise.Promise}` for an example of using the returned Promise. * * @since 6.0.0 */Ext.define('Ext.Deferred', function (Deferred) {    var ExtPromise,        when; return {    extend: 'Ext.promise.Deferred',     requires: [        'Ext.Promise'    ],     statics: {        _ready: function () {            // Our requires are met, so we can cache Ext.promise.Deferred             ExtPromise = Ext.promise.Promise;            when = Ext.Promise.resolve;        },         /**         * Returns a new Promise that will only resolve once all the specified         * `promisesOrValues` have resolved.         *         * The resolution value will be an Array containing the resolution value of each         * of the `promisesOrValues`.         *         * @param {Mixed[]/Ext.promise.Promise[]/Ext.promise.Promise} promisesOrValues An         * Array of values or Promises, or a Promise of an Array of values or Promises.         * @return {Ext.promise.Promise} A Promise of an Array of the resolved values.         * @static         */        all: function () {            return ExtPromise.all.apply(ExtPromise, arguments);        },         /**         * Initiates a competitive race, returning a new Promise that will resolve when         * any one of the specified `promisesOrValues` have resolved, or will reject when         * all `promisesOrValues` have rejected or cancelled.         *         * The resolution value will the first value of `promisesOrValues` to resolve.         *         * @param {Mixed[]/Ext.promise.Promise[]/Ext.promise.Promise} promisesOrValues An         * Array of values or Promises, or a Promise of an Array of values or Promises.         * @return {Ext.promise.Promise} A Promise of the first resolved value.         * @static         */        any: function (promisesOrValues) {            //<debug>             if (!(Ext.isArray(promisesOrValues) || ExtPromise.is(promisesOrValues))) {                Ext.raise('Invalid parameter: expected an Array or Promise of an Array.');            }            //</debug>              return Deferred.some(promisesOrValues, 1).then(function (array) {                return array[0];            }, function (error) {                if (error instanceof Error &&                    error.message === 'Too few Promises were resolved.') {                    Ext.raise('No Promises were resolved.');                }                else {                    throw error;                }            });        },         /**         * Returns a new Promise that will automatically resolve with the specified         * Promise or value after the specified delay (in milliseconds).         *         * @param {Mixed} promiseOrValue A Promise or value.         * @param {Number} milliseconds A delay duration (in milliseconds).         * @return {Ext.promise.Promise} A Promise of the specified Promise or value that         * will resolve after the specified delay.         * @static         */        delay: function (promiseOrValue, milliseconds) {            var deferred;             if (arguments.length === 1) {                milliseconds = promiseOrValue;                promiseOrValue = undefined;            }             milliseconds = Math.max(milliseconds, 0);             deferred = new Deferred();             setTimeout(function () {                deferred.resolve(promiseOrValue);            }, milliseconds);             return deferred.promise;        },         /**         * Traditional map function, similar to `Array.prototype.map()`, that allows         * input to contain promises and/or values.         *         * The specified map function may return either a value or a promise.         *         * @param {Mixed[]/Ext.promise.Promise[]/Ext.promise.Promise} promisesOrValues An         * Array of values or Promises, or a Promise of an Array of values or Promises.         * @param {Function} mapFn A Function to call to transform each resolved value in         * the Array.         * @return {Ext.promise.Promise} A Promise of an Array of the mapped resolved         * values.         * @static         */        map: function (promisesOrValues, mapFn) {            //<debug>             if (!(Ext.isArray(promisesOrValues) || ExtPromise.is(promisesOrValues))) {                Ext.raise('Invalid parameter: expected an Array or Promise of an Array.');            }             if (!Ext.isFunction(mapFn)) {                Ext.raise('Invalid parameter: expected a function.');            }            //</debug>              return Deferred.resolved(promisesOrValues).then(function (promisesOrValues) {                var deferred, index, promiseOrValue, remainingToResolve, resolve, results, i, len;                 remainingToResolve = promisesOrValues.length;                results = new Array(promisesOrValues.length);                deferred = new Deferred();                 if (!remainingToResolve) {                    deferred.resolve(results);                }                else {                    resolve = function (item, index) {                        return Deferred.resolved(item).then(function (value) {                            return mapFn(value, index, results);                        }).then(function (value) {                            results[index] = value;                            if (!--remainingToResolve) {                                deferred.resolve(results);                            }                            return value;                        }, function (reason) {                            return deferred.reject(reason);                        });                    };                    for (index = i = 0, len = promisesOrValues.length; i < len; index = ++i) {                        promiseOrValue = promisesOrValues[index];                         if (index in promisesOrValues) {                            resolve(promiseOrValue, index);                        }                        else {                            remainingToResolve--;                        }                    }                }                 return deferred.promise;            });        },         /**         * Returns a new function that wraps the specified function and caches the         * results for previously processed inputs.         *         * Similar to {@link Ext.Function#memoize Ext.Function.memoize()}, except it         * allows for parameters that are Promises and/or values.         *         * @param {Function} fn A Function to wrap.         * @param {Object} scope An optional scope in which to execute the wrapped function.         * @param {Function} hashFn An optional function used to compute a hash key for         * storing the result, based on the arguments to the original function.         * @return {Function} The new wrapper function.         * @static         */        memoize: function (fn, scope, hashFn) {            var memoizedFn = Ext.Function.memoize(fn, scope, hashFn);             return function () {                return Deferred.all(Ext.Array.slice(arguments)).then(function (values) {                    return memoizedFn.apply(scope, values);                });            };        },         /**         * Execute an Array (or {@link Ext.promise.Promise Promise} of an Array) of         * functions in parallel.         *         * The specified functions may optionally return their results as         * {@link Ext.promise.Promise Promises}.         *         * @param {Function[]/Ext.promise.Promise} fns The Array (or Promise of an Array)         * of functions to execute.         * @param {Object} scope Optional scope in which to execute the specified functions.         * @return {Ext.promise.Promise} Promise of an Array of results for each function         * call (in the same order).         * @static         */        parallel: function (fns, scope) {            if (scope == null) {                scope = null;            }             var args = Ext.Array.slice(arguments, 2);             return Deferred.map(fns, function (fn) {                if (!Ext.isFunction(fn)) {                    throw new Error('Invalid parameter: expected a function.');                }                 return fn.apply(scope, args);            });        },         /**         * Execute an Array (or {@link Ext.promise.Promise Promise} of an Array) of         * functions as a pipeline, where each function's result is passed to the         * subsequent function as input.         *         * The specified functions may optionally return their results as         * {@link Ext.promise.Promise Promises}.         *         * @param {Function[]/Ext.promise.Promise} fns The Array (or Promise of an Array)         * of functions to execute.         * @param {Object} initialValue Initial value to be passed to the first function         * in the pipeline.         * @param {Object} scope Optional scope in which to execute the specified functions.         * @return {Ext.promise.Promise} Promise of the result value for the final         * function in the pipeline.         * @static         */        pipeline: function (fns, initialValue, scope) {            if (scope == null) {                scope = null;            }             return Deferred.reduce(fns, function (value, fn) {                if (!Ext.isFunction(fn)) {                    throw new Error('Invalid parameter: expected a function.');                }                 return fn.call(scope, value);            }, initialValue);        },         /**         * Traditional reduce function, similar to `Array.reduce()`, that allows input to         * contain promises and/or values.         *         * @param {Mixed[]/Ext.promise.Promise[]/Ext.promise.Promise} values An         * Array of values or Promises, or a Promise of an Array of values or Promises.         * @param {Function} reduceFn A Function to call to transform each successive         * item in the Array into the final reduced value.         * @param {Mixed} initialValue An initial Promise or value.         * @return {Ext.promise.Promise} A Promise of the reduced value.         * @static         */        reduce: function (values, reduceFn, initialValue) {            //<debug>             if (!(Ext.isArray(values) || ExtPromise.is(values))) {                Ext.raise('Invalid parameter: expected an Array or Promise of an Array.');            }             if (!Ext.isFunction(reduceFn)) {                Ext.raise('Invalid parameter: expected a function.');            }            //</debug>              var initialValueSpecified = arguments.length === 3;             return Deferred.resolved(values).then(function (promisesOrValues) {                var reduceArguments = [                    promisesOrValues,                    function (previousValueOrPromise, currentValueOrPromise, currentIndex) {                        return Deferred.resolved(previousValueOrPromise).then(function (previousValue) {                            return Deferred.resolved(currentValueOrPromise).then(function (currentValue) {                                return reduceFn(previousValue, currentValue, currentIndex, promisesOrValues);                            });                        });                    }                ];                 if (initialValueSpecified) {                    reduceArguments.push(initialValue);                }                 return Ext.Array.reduce.apply(Ext.Array, reduceArguments);            });        },         /**         * Convenience method that returns a new Promise rejected with the specified         * reason.         *         * @param {Error} reason Rejection reason.         * @return {Ext.promise.Promise} The rejected Promise.         * @static         */        rejected: function (reason) {            var deferred = new Ext.Deferred();             deferred.reject(reason);             return deferred.promise;        },         /**         * Returns a new Promise that either         *         *  * Resolves immediately for the specified value, or         *  * Resolves or rejects when the specified promise (or third-party Promise or         *    then()-able) is resolved or rejected.         *         * @param {Mixed} promiseOrValue A Promise (or third-party Promise or then()-able)         * or value.         * @return {Ext.promise.Promise} A Promise of the specified Promise or value.         * @static         */        resolved: function (value) {            var deferred = new Ext.Deferred();             deferred.resolve(value);             return deferred.promise;        },         /**         * Execute an Array (or {@link Ext.promise.Promise Promise} of an Array) of         * functions sequentially.         *         * The specified functions may optionally return their results as {@link         * Ext.promise.Promise Promises}.         *         * @param {Function[]/Ext.promise.Promise} fns The Array (or Promise of an Array)         * of functions to execute.         * @param {Object} scope Optional scope in which to execute the specified functions.         * @return {Ext.promise.Promise} Promise of an Array of results for each function         * call (in the same order).         * @static         */        sequence: function (fns, scope) {            if (scope == null) {                scope = null;            }             var args = Ext.Array.slice(arguments, 2);             return Deferred.reduce(fns, function (results, fn) {                if (!Ext.isFunction(fn)) {                    throw new Error('Invalid parameter: expected a function.');                }                 return Deferred.resolved(fn.apply(scope, args)).then(function (result) {                    results.push(result);                     return results;                });            }, []);        },         /**         * Initiates a competitive race, returning a new Promise that will resolve when         * `howMany` of the specified `promisesOrValues` have resolved, or will reject         * when it becomes impossible for `howMany` to resolve.         *         * The resolution value will be an Array of the first `howMany` values of         * `promisesOrValues` to resolve.         *         * @param {Mixed[]/Ext.promise.Promise[]/Ext.promise.Promise} promisesOrValues An         * Array of values or Promises, or a Promise of an Array of values or Promises.         * @param {Number} howMany The expected number of resolved values.         * @return {Ext.promise.Promise} A Promise of the expected number of resolved         * values.         * @static         */        some: function (promisesOrValues, howMany) {            //<debug>             if (!(Ext.isArray(promisesOrValues) || ExtPromise.is(promisesOrValues))) {                Ext.raise('Invalid parameter: expected an Array or Promise of an Array.');            }             if (!Ext.isNumeric(howMany) || howMany <= 0) {                Ext.raise('Invalid parameter: expected a positive integer.');            }            //</debug>              return Deferred.resolved(promisesOrValues).then(function (promisesOrValues) {                var deferred, index, onReject, onResolve, promiseOrValue,                    remainingToReject, remainingToResolve, values, i, len;                 values = [];                remainingToResolve = howMany;                remainingToReject = (promisesOrValues.length - remainingToResolve) + 1;                deferred = new Deferred();                 if (promisesOrValues.length < howMany) {                    deferred.reject(new Error('Too few Promises were resolved.'));                }                else {                    onResolve = function (value) {                        if (remainingToResolve > 0) {                            values.push(value);                        }                         remainingToResolve--;                         if (remainingToResolve === 0) {                            deferred.resolve(values);                        }                         return value;                    };                     onReject = function (reason) {                        remainingToReject--;                         if (remainingToReject === 0) {                            deferred.reject(new Error('Too few Promises were resolved.'));                        }                         return reason;                    };                     for (index = i = 0, len = promisesOrValues.length; i < len; index = ++i) {                        promiseOrValue = promisesOrValues[index];                         if (index in promisesOrValues) {                            Deferred.resolved(promiseOrValue).then(onResolve, onReject);                        }                    }                }                 return deferred.promise;            });        },         /**         * Returns a new Promise that will automatically reject after the specified         * timeout (in milliseconds) if the specified promise has not resolved or         * rejected.         *         * @param {Mixed} promiseOrValue A Promise or value.         * @param {Number} milliseconds A timeout duration (in milliseconds).         * @return {Ext.promise.Promise} A Promise of the specified Promise or value that         * enforces the specified timeout.         * @static         */        timeout: function (promiseOrValue, milliseconds) {            var deferred = new Deferred(),                timeoutId;             timeoutId = setTimeout(function () {                if (timeoutId) {                    deferred.reject(new Error('Promise timed out.'));                }            }, milliseconds);             Deferred.resolved(promiseOrValue).then(function (value) {                clearTimeout(timeoutId);                timeoutId = null;                deferred.resolve(value);            }, function (reason) {                clearTimeout(timeoutId);                timeoutId = null;                deferred.reject(reason);            });             return deferred.promise;        }    }}},function (Deferred) {    Deferred._ready();});