Permalink
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up| 'use strict'; | |
| /** | |
| * @ngdoc service | |
| * @name $q | |
| * @requires $rootScope | |
| * | |
| * @description | |
| * A service that helps you run functions asynchronously, and use their return values (or exceptions) | |
| * when they are done processing. | |
| * | |
| * This is a [Promises/A+](https://promisesaplus.com/)-compliant implementation of promises/deferred | |
| * objects inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). | |
| * | |
| * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred | |
| * implementations, and the other which resembles ES6 (ES2015) promises to some degree. | |
| * | |
| * ## $q constructor | |
| * | |
| * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` | |
| * function as the first argument. This is similar to the native Promise implementation from ES6, | |
| * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). | |
| * | |
| * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are | |
| * available yet. | |
| * | |
| * It can be used like so: | |
| * | |
| * ```js | |
| * // for the purpose of this example let's assume that variables `$q` and `okToGreet` | |
| * // are available in the current lexical scope (they could have been injected or passed in). | |
| * | |
| * function asyncGreet(name) { | |
| * // perform some asynchronous operation, resolve or reject the promise when appropriate. | |
| * return $q(function(resolve, reject) { | |
| * setTimeout(function() { | |
| * if (okToGreet(name)) { | |
| * resolve('Hello, ' + name + '!'); | |
| * } else { | |
| * reject('Greeting ' + name + ' is not allowed.'); | |
| * } | |
| * }, 1000); | |
| * }); | |
| * } | |
| * | |
| * var promise = asyncGreet('Robin Hood'); | |
| * promise.then(function(greeting) { | |
| * alert('Success: ' + greeting); | |
| * }, function(reason) { | |
| * alert('Failed: ' + reason); | |
| * }); | |
| * ``` | |
| * | |
| * Note: progress/notify callbacks are not currently supported via the ES6-style interface. | |
| * | |
| * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise. | |
| * | |
| * However, the more traditional CommonJS-style usage is still available, and documented below. | |
| * | |
| * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an | |
| * interface for interacting with an object that represents the result of an action that is | |
| * performed asynchronously, and may or may not be finished at any given point in time. | |
| * | |
| * From the perspective of dealing with error handling, deferred and promise APIs are to | |
| * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. | |
| * | |
| * ```js | |
| * // for the purpose of this example let's assume that variables `$q` and `okToGreet` | |
| * // are available in the current lexical scope (they could have been injected or passed in). | |
| * | |
| * function asyncGreet(name) { | |
| * var deferred = $q.defer(); | |
| * | |
| * setTimeout(function() { | |
| * deferred.notify('About to greet ' + name + '.'); | |
| * | |
| * if (okToGreet(name)) { | |
| * deferred.resolve('Hello, ' + name + '!'); | |
| * } else { | |
| * deferred.reject('Greeting ' + name + ' is not allowed.'); | |
| * } | |
| * }, 1000); | |
| * | |
| * return deferred.promise; | |
| * } | |
| * | |
| * var promise = asyncGreet('Robin Hood'); | |
| * promise.then(function(greeting) { | |
| * alert('Success: ' + greeting); | |
| * }, function(reason) { | |
| * alert('Failed: ' + reason); | |
| * }, function(update) { | |
| * alert('Got notification: ' + update); | |
| * }); | |
| * ``` | |
| * | |
| * At first it might not be obvious why this extra complexity is worth the trouble. The payoff | |
| * comes in the way of guarantees that promise and deferred APIs make, see | |
| * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. | |
| * | |
| * Additionally the promise api allows for composition that is very hard to do with the | |
| * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. | |
| * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the | |
| * section on serial or parallel joining of promises. | |
| * | |
| * ## The Deferred API | |
| * | |
| * A new instance of deferred is constructed by calling `$q.defer()`. | |
| * | |
| * The purpose of the deferred object is to expose the associated Promise instance as well as APIs | |
| * that can be used for signaling the successful or unsuccessful completion, as well as the status | |
| * of the task. | |
| * | |
| * **Methods** | |
| * | |
| * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection | |
| * constructed via `$q.reject`, the promise will be rejected instead. | |
| * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to | |
| * resolving it with a rejection constructed via `$q.reject`. | |
| * - `notify(value)` - provides updates on the status of the promise's execution. This may be called | |
| * multiple times before the promise is either resolved or rejected. | |
| * | |
| * **Properties** | |
| * | |
| * - promise – `{Promise}` – promise object associated with this deferred. | |
| * | |
| * | |
| * ## The Promise API | |
| * | |
| * A new promise instance is created when a deferred instance is created and can be retrieved by | |
| * calling `deferred.promise`. | |
| * | |
| * The purpose of the promise object is to allow for interested parties to get access to the result | |
| * of the deferred task when it completes. | |
| * | |
| * **Methods** | |
| * | |
| * - `then(successCallback, [errorCallback], [notifyCallback])` – regardless of when the promise was or | |
| * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously | |
| * as soon as the result is available. The callbacks are called with a single argument: the result | |
| * or rejection reason. Additionally, the notify callback may be called zero or more times to | |
| * provide a progress indication, before the promise is resolved or rejected. | |
| * | |
| * This method *returns a new promise* which is resolved or rejected via the return value of the | |
| * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved | |
| * with the value which is resolved in that promise using | |
| * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). | |
| * It also notifies via the return value of the `notifyCallback` method. The promise cannot be | |
| * resolved or rejected from the notifyCallback method. The errorCallback and notifyCallback | |
| * arguments are optional. | |
| * | |
| * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` | |
| * | |
| * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise, | |
| * but to do so without modifying the final value. This is useful to release resources or do some | |
| * clean-up that needs to be done whether the promise was rejected or resolved. See the [full | |
| * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for | |
| * more information. | |
| * | |
| * ## Chaining promises | |
| * | |
| * Because calling the `then` method of a promise returns a new derived promise, it is easily | |
| * possible to create a chain of promises: | |
| * | |
| * ```js | |
| * promiseB = promiseA.then(function(result) { | |
| * return result + 1; | |
| * }); | |
| * | |
| * // promiseB will be resolved immediately after promiseA is resolved and its value | |
| * // will be the result of promiseA incremented by 1 | |
| * ``` | |
| * | |
| * It is possible to create chains of any length and since a promise can be resolved with another | |
| * promise (which will defer its resolution further), it is possible to pause/defer resolution of | |
| * the promises at any point in the chain. This makes it possible to implement powerful APIs like | |
| * $http's response interceptors. | |
| * | |
| * | |
| * ## Differences between Kris Kowal's Q and $q | |
| * | |
| * There are two main differences: | |
| * | |
| * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation | |
| * mechanism in AngularJS, which means faster propagation of resolution or rejection into your | |
| * models and avoiding unnecessary browser repaints, which would result in flickering UI. | |
| * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains | |
| * all the important functionality needed for common async tasks. | |
| * | |
| * ## Testing | |
| * | |
| * ```js | |
| * it('should simulate promise', inject(function($q, $rootScope) { | |
| * var deferred = $q.defer(); | |
| * var promise = deferred.promise; | |
| * var resolvedValue; | |
| * | |
| * promise.then(function(value) { resolvedValue = value; }); | |
| * expect(resolvedValue).toBeUndefined(); | |
| * | |
| * // Simulate resolving of promise | |
| * deferred.resolve(123); | |
| * // Note that the 'then' function does not get called synchronously. | |
| * // This is because we want the promise API to always be async, whether or not | |
| * // it got called synchronously or asynchronously. | |
| * expect(resolvedValue).toBeUndefined(); | |
| * | |
| * // Propagate promise resolution to 'then' functions using $apply(). | |
| * $rootScope.$apply(); | |
| * expect(resolvedValue).toEqual(123); | |
| * })); | |
| * ``` | |
| * | |
| * @param {function(function, function)} resolver Function which is responsible for resolving or | |
| * rejecting the newly created promise. The first parameter is a function which resolves the | |
| * promise, the second parameter is a function which rejects the promise. | |
| * | |
| * @returns {Promise} The newly created promise. | |
| */ | |
| /** | |
| * @ngdoc provider | |
| * @name $qProvider | |
| * @this | |
| * | |
| * @description | |
| */ | |
| function $QProvider() { | |
| var errorOnUnhandledRejections = true; | |
| this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { | |
| return qFactory(function(callback) { | |
| $rootScope.$evalAsync(callback); | |
| }, $exceptionHandler, errorOnUnhandledRejections); | |
| }]; | |
| /** | |
| * @ngdoc method | |
| * @name $qProvider#errorOnUnhandledRejections | |
| * @kind function | |
| * | |
| * @description | |
| * Retrieves or overrides whether to generate an error when a rejected promise is not handled. | |
| * This feature is enabled by default. | |
| * | |
| * @param {boolean=} value Whether to generate an error when a rejected promise is not handled. | |
| * @returns {boolean|ng.$qProvider} Current value when called without a new value or self for | |
| * chaining otherwise. | |
| */ | |
| this.errorOnUnhandledRejections = function(value) { | |
| if (isDefined(value)) { | |
| errorOnUnhandledRejections = value; | |
| return this; | |
| } else { | |
| return errorOnUnhandledRejections; | |
| } | |
| }; | |
| } | |
| /** @this */ | |
| function $$QProvider() { | |
| var errorOnUnhandledRejections = true; | |
| this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { | |
| return qFactory(function(callback) { | |
| $browser.defer(callback); | |
| }, $exceptionHandler, errorOnUnhandledRejections); | |
| }]; | |
| this.errorOnUnhandledRejections = function(value) { | |
| if (isDefined(value)) { | |
| errorOnUnhandledRejections = value; | |
| return this; | |
| } else { | |
| return errorOnUnhandledRejections; | |
| } | |
| }; | |
| } | |
| /** | |
| * Constructs a promise manager. | |
| * | |
| * @param {function(function)} nextTick Function for executing functions in the next turn. | |
| * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for | |
| * debugging purposes. | |
| * @param {boolean=} errorOnUnhandledRejections Whether an error should be generated on unhandled | |
| * promises rejections. | |
| * @returns {object} Promise manager. | |
| */ | |
| function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { | |
| var $qMinErr = minErr('$q', TypeError); | |
| var queueSize = 0; | |
| var checkQueue = []; | |
| /** | |
| * @ngdoc method | |
| * @name ng.$q#defer | |
| * @kind function | |
| * | |
| * @description | |
| * Creates a `Deferred` object which represents a task which will finish in the future. | |
| * | |
| * @returns {Deferred} Returns a new instance of deferred. | |
| */ | |
| function defer() { | |
| return new Deferred(); | |
| } | |
| function Deferred() { | |
| var promise = this.promise = new Promise(); | |
| //Non prototype methods necessary to support unbound execution :/ | |
| this.resolve = function(val) { resolvePromise(promise, val); }; | |
| this.reject = function(reason) { rejectPromise(promise, reason); }; | |
| this.notify = function(progress) { notifyPromise(promise, progress); }; | |
| } | |
| function Promise() { | |
| this.$$state = { status: 0 }; | |
| } | |
| extend(Promise.prototype, { | |
| then: function(onFulfilled, onRejected, progressBack) { | |
| if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { | |
| return this; | |
| } | |
| var result = new Promise(); | |
| this.$$state.pending = this.$$state.pending || []; | |
| this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); | |
| if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); | |
| return result; | |
| }, | |
| 'catch': function(callback) { | |
| return this.then(null, callback); | |
| }, | |
| 'finally': function(callback, progressBack) { | |
| return this.then(function(value) { | |
| return handleCallback(value, resolve, callback); | |
| }, function(error) { | |
| return handleCallback(error, reject, callback); | |
| }, progressBack); | |
| } | |
| }); | |
| function processQueue(state) { | |
| var fn, promise, pending; | |
| pending = state.pending; | |
| state.processScheduled = false; | |
| state.pending = undefined; | |
| try { | |
| for (var i = 0, ii = pending.length; i < ii; ++i) { | |
| markQStateExceptionHandled(state); | |
| promise = pending[i][0]; | |
| fn = pending[i][state.status]; | |
| try { | |
| if (isFunction(fn)) { | |
| resolvePromise(promise, fn(state.value)); | |
| } else if (state.status === 1) { | |
| resolvePromise(promise, state.value); | |
| } else { | |
| rejectPromise(promise, state.value); | |
| } | |
| } catch (e) { | |
| rejectPromise(promise, e); | |
| // This error is explicitly marked for being passed to the $exceptionHandler | |
| if (e && e.$$passToExceptionHandler === true) { | |
| exceptionHandler(e); | |
| } | |
| } | |
| } | |
| } finally { | |
| --queueSize; | |
| if (errorOnUnhandledRejections && queueSize === 0) { | |
| nextTick(processChecks); | |
| } | |
| } | |
| } | |
| function processChecks() { | |
| // eslint-disable-next-line no-unmodified-loop-condition | |
| while (!queueSize && checkQueue.length) { | |
| var toCheck = checkQueue.shift(); | |
| if (!isStateExceptionHandled(toCheck)) { | |
| markQStateExceptionHandled(toCheck); | |
| var errorMessage = 'Possibly unhandled rejection: ' + toDebugString(toCheck.value); | |
| if (isError(toCheck.value)) { | |
| exceptionHandler(toCheck.value, errorMessage); | |
| } else { | |
| exceptionHandler(errorMessage); | |
| } | |
| } | |
| } | |
| } | |
| function scheduleProcessQueue(state) { | |
| if (errorOnUnhandledRejections && !state.pending && state.status === 2 && !isStateExceptionHandled(state)) { | |
| if (queueSize === 0 && checkQueue.length === 0) { | |
| nextTick(processChecks); | |
| } | |
| checkQueue.push(state); | |
| } | |
| if (state.processScheduled || !state.pending) return; | |
| state.processScheduled = true; | |
| ++queueSize; | |
| nextTick(function() { processQueue(state); }); | |
| } | |
| function resolvePromise(promise, val) { | |
| if (promise.$$state.status) return; | |
| if (val === promise) { | |
| $$reject(promise, $qMinErr( | |
| 'qcycle', | |
| 'Expected promise to be resolved with value other than itself \'{0}\'', | |
| val)); | |
| } else { | |
| $$resolve(promise, val); | |
| } | |
| } | |
| function $$resolve(promise, val) { | |
| var then; | |
| var done = false; | |
| try { | |
| if (isObject(val) || isFunction(val)) then = val.then; | |
| if (isFunction(then)) { | |
| promise.$$state.status = -1; | |
| then.call(val, doResolve, doReject, doNotify); | |
| } else { | |
| promise.$$state.value = val; | |
| promise.$$state.status = 1; | |
| scheduleProcessQueue(promise.$$state); | |
| } | |
| } catch (e) { | |
| doReject(e); | |
| } | |
| function doResolve(val) { | |
| if (done) return; | |
| done = true; | |
| $$resolve(promise, val); | |
| } | |
| function doReject(val) { | |
| if (done) return; | |
| done = true; | |
| $$reject(promise, val); | |
| } | |
| function doNotify(progress) { | |
| notifyPromise(promise, progress); | |
| } | |
| } | |
| function rejectPromise(promise, reason) { | |
| if (promise.$$state.status) return; | |
| $$reject(promise, reason); | |
| } | |
| function $$reject(promise, reason) { | |
| promise.$$state.value = reason; | |
| promise.$$state.status = 2; | |
| scheduleProcessQueue(promise.$$state); | |
| } | |
| function notifyPromise(promise, progress) { | |
| var callbacks = promise.$$state.pending; | |
| if ((promise.$$state.status <= 0) && callbacks && callbacks.length) { | |
| nextTick(function() { | |
| var callback, result; | |
| for (var i = 0, ii = callbacks.length; i < ii; i++) { | |
| result = callbacks[i][0]; | |
| callback = callbacks[i][3]; | |
| try { | |
| notifyPromise(result, isFunction(callback) ? callback(progress) : progress); | |
| } catch (e) { | |
| exceptionHandler(e); | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| /** | |
| * @ngdoc method | |
| * @name $q#reject | |
| * @kind function | |
| * | |
| * @description | |
| * Creates a promise that is resolved as rejected with the specified `reason`. This api should be | |
| * used to forward rejection in a chain of promises. If you are dealing with the last promise in | |
| * a promise chain, you don't need to worry about it. | |
| * | |
| * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of | |
| * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via | |
| * a promise error callback and you want to forward the error to the promise derived from the | |
| * current promise, you have to "rethrow" the error by returning a rejection constructed via | |
| * `reject`. | |
| * | |
| * ```js | |
| * promiseB = promiseA.then(function(result) { | |
| * // success: do something and resolve promiseB | |
| * // with the old or a new result | |
| * return result; | |
| * }, function(reason) { | |
| * // error: handle the error if possible and | |
| * // resolve promiseB with newPromiseOrValue, | |
| * // otherwise forward the rejection to promiseB | |
| * if (canHandle(reason)) { | |
| * // handle the error and recover | |
| * return newPromiseOrValue; | |
| * } | |
| * return $q.reject(reason); | |
| * }); | |
| * ``` | |
| * | |
| * @param {*} reason Constant, message, exception or an object representing the rejection reason. | |
| * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. | |
| */ | |
| function reject(reason) { | |
| var result = new Promise(); | |
| rejectPromise(result, reason); | |
| return result; | |
| } | |
| function handleCallback(value, resolver, callback) { | |
| var callbackOutput = null; | |
| try { | |
| if (isFunction(callback)) callbackOutput = callback(); | |
| } catch (e) { | |
| return reject(e); | |
| } | |
| if (isPromiseLike(callbackOutput)) { | |
| return callbackOutput.then(function() { | |
| return resolver(value); | |
| }, reject); | |
| } else { | |
| return resolver(value); | |
| } | |
| } | |
| /** | |
| * @ngdoc method | |
| * @name $q#when | |
| * @kind function | |
| * | |
| * @description | |
| * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. | |
| * This is useful when you are dealing with an object that might or might not be a promise, or if | |
| * the promise comes from a source that can't be trusted. | |
| * | |
| * @param {*} value Value or a promise | |
| * @param {Function=} successCallback | |
| * @param {Function=} errorCallback | |
| * @param {Function=} progressCallback | |
| * @returns {Promise} Returns a promise of the passed value or promise | |
| */ | |
| function when(value, callback, errback, progressBack) { | |
| var result = new Promise(); | |
| resolvePromise(result, value); | |
| return result.then(callback, errback, progressBack); | |
| } | |
| /** | |
| * @ngdoc method | |
| * @name $q#resolve | |
| * @kind function | |
| * | |
| * @description | |
| * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. | |
| * | |
| * @param {*} value Value or a promise | |
| * @param {Function=} successCallback | |
| * @param {Function=} errorCallback | |
| * @param {Function=} progressCallback | |
| * @returns {Promise} Returns a promise of the passed value or promise | |
| */ | |
| var resolve = when; | |
| /** | |
| * @ngdoc method | |
| * @name $q#all | |
| * @kind function | |
| * | |
| * @description | |
| * Combines multiple promises into a single promise that is resolved when all of the input | |
| * promises are resolved. | |
| * | |
| * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. | |
| * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, | |
| * each value corresponding to the promise at the same index/key in the `promises` array/hash. | |
| * If any of the promises is resolved with a rejection, this resulting promise will be rejected | |
| * with the same rejection value. | |
| */ | |
| function all(promises) { | |
| var result = new Promise(), | |
| counter = 0, | |
| results = isArray(promises) ? [] : {}; | |
| forEach(promises, function(promise, key) { | |
| counter++; | |
| when(promise).then(function(value) { | |
| results[key] = value; | |
| if (!(--counter)) resolvePromise(result, results); | |
| }, function(reason) { | |
| rejectPromise(result, reason); | |
| }); | |
| }); | |
| if (counter === 0) { | |
| resolvePromise(result, results); | |
| } | |
| return result; | |
| } | |
| /** | |
| * @ngdoc method | |
| * @name $q#race | |
| * @kind function | |
| * | |
| * @description | |
| * Returns a promise that resolves or rejects as soon as one of those promises | |
| * resolves or rejects, with the value or reason from that promise. | |
| * | |
| * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. | |
| * @returns {Promise} a promise that resolves or rejects as soon as one of the `promises` | |
| * resolves or rejects, with the value or reason from that promise. | |
| */ | |
| function race(promises) { | |
| var deferred = defer(); | |
| forEach(promises, function(promise) { | |
| when(promise).then(deferred.resolve, deferred.reject); | |
| }); | |
| return deferred.promise; | |
| } | |
| function $Q(resolver) { | |
| if (!isFunction(resolver)) { | |
| throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver); | |
| } | |
| var promise = new Promise(); | |
| function resolveFn(value) { | |
| resolvePromise(promise, value); | |
| } | |
| function rejectFn(reason) { | |
| rejectPromise(promise, reason); | |
| } | |
| resolver(resolveFn, rejectFn); | |
| return promise; | |
| } | |
| // Let's make the instanceof operator work for promises, so that | |
| // `new $q(fn) instanceof $q` would evaluate to true. | |
| $Q.prototype = Promise.prototype; | |
| $Q.defer = defer; | |
| $Q.reject = reject; | |
| $Q.when = when; | |
| $Q.resolve = resolve; | |
| $Q.all = all; | |
| $Q.race = race; | |
| return $Q; | |
| } | |
| function isStateExceptionHandled(state) { | |
| return !!state.pur; | |
| } | |
| function markQStateExceptionHandled(state) { | |
| state.pur = true; | |
| } | |
| function markQExceptionHandled(q) { | |
| markQStateExceptionHandled(q.$$state); | |
| } |