/* 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 (promiseOrValue) { var deferred = new Ext.Deferred(); deferred.resolve(promiseOrValue); 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();});