'use strict'; /** * Constructs a promise manager. * * @param {function(function)=} nextTick Function for executing functions in the next turn. Falls * back to `setTimeout` if undefined. * @param {function(...*)=} exceptionHandler Function into which unexpected exceptions are passed for * debugging purposes. Falls back to `console.error` if undefined, * @returns {object} Promise manager. */ function qFactory(nextTick, exceptionHandler) { nextTick = nextTick || function(callback) { setTimeout(callback, 0); // very rare since most of queueing will be handled within $apply }; exceptionHandler = exceptionHandler || function(e) { // TODO(i): console.error is somehow reset to function(a) {}, it might be a JSTD bug if (console && console.log) { console.log(e); } } /** * @ngdoc * @name angular.module.ng.$q#defer * @methodOf angular.module.ng.$q * @description * Creates a `Deferred` object which represents a task which will finish in the future. * * @returns {Deferred} Returns a new instance of deferred. */ var defer = function() { var pending = [], value, deferred; deferred = { resolve: function(val) { if (pending) { var callbacks = pending; pending = undefined; value = ref(val); if (callbacks.length) { nextTick(function() { var callback; for (var i = 0, ii = callbacks.length; i < ii; i++) { callback = callbacks[i]; value.then(callback[0], callback[1]); } }); } } }, reject: function(reason) { deferred.resolve(reject(reason)); }, promise: { then: function(callback, errback) { var result = defer(); var wrappedCallback = function(value) { try { result.resolve((callback || defaultCallback)(value)); } catch(e) { exceptionHandler(e); result.reject(e); } }; var wrappedErrback = function(reason) { try { result.resolve((errback || defaultErrback)(reason)); } catch(e) { exceptionHandler(e); result.reject(e); } }; if (pending) { pending.push([wrappedCallback, wrappedErrback]); } else { value.then(wrappedCallback, wrappedErrback); } return result.promise; } } }; return deferred; }; var ref = function(value) { if (value && value.then) return value; return { then: function(callback) { var result = defer(); nextTick(function() { result.resolve(callback(value)); }); return result.promise; } }; }; /** * @ngdoc * @name angular.module.ng.$q#reject * @methodOf angular.module.ng.$q * @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`. * *
* 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`.
*/
var reject = function(reason) {
return {
then: function(callback, errback) {
var result = defer();
nextTick(function() {
result.resolve(errback(reason));
});
return result.promise;
}
};
};
/**
* @ngdoc
* @name angular.module.ng.$q#when
* @methodOf angular.module.ng.$q
* @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 on 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
* @returns {Promise} Returns a single promise that will be resolved with an array of values,
* each value coresponding to the promise at the same index in the `promises` array. If any of
* the promises is resolved with a rejection, this resulting promise will be resolved with the
* same rejection.
*/
var when = function(value, callback, errback) {
var result = defer(),
done;
var wrappedCallback = function(value) {
try {
return (callback || defaultCallback)(value);
} catch (e) {
exceptionHandler(e);
return reject(e);
}
};
var wrappedErrback = function(reason) {
try {
return (errback || defaultErrback)(reason);
} catch (e) {
exceptionHandler(e);
return reject(e);
}
};
nextTick(function() {
ref(value).then(function(value) {
if (done) return;
done = true;
result.resolve(ref(value).then(wrappedCallback, wrappedErrback));
}, function(reason) {
if (done) return;
done = true;
result.resolve(wrappedErrback(reason));
});
});
return result.promise;
};
function defaultCallback(value) {
return value;
}
function defaultErrback(reason) {
return reject(reason);
}
/**
* @ngdoc
* @name angular.module.ng.$q#all
* @methodOf angular.module.ng.$q
* @description
* Combines multiple promises into a single promise that is resolved when all of the input
* promises are resolved.
*
* @param {Array.
* function asyncGreet(name) {
* var deferred = $q.defer();
*
* setTimeout(function() {
* 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);
* );
*
*
* 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](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 paralel 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 api's
* that can be used for singnaling the successful or unsucessful completion 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`.
*
* **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)` – regardless of when the promise was or will be resolved
* or rejected calls one of the success or error callbacks asynchronously as soon as the result
* is available.
*
* This method *returns a new promise* which is resolved or rejected via the return value of the
* `successCallback` or `errorCallback`.
*
*
* # Differences betweeb Kris Kowal's Q and $q
*
* There are three main differences:
*
* - $q is integrated with the {@link angular.module.ng.$rootScope.Scope} Scope model observation
* mechanism in angular, which means faster propagation of resolution or rejection into your
* models and avoiding unnecessary browser redraws, which would result in flickering UI.
* - $q promises are reconginized by the templating engine in angular, which means that in templates
* you can treat promises attached to a scope as if they were the resulting values.
* - Q has many more features that $q, but that comes at a cost of bytes. $q is tiny, but contains
* all the important functionality needed for common async tasks.
*/
// TODO(i): move elsewhere
function $QProvider() {
this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
return qFactory(function(callback) {
$rootScope.$evalAsync(callback);
}, $exceptionHandler);
}];
}