2011-11-08 09:56:42 +00:00
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-12-12 16:07:39 +00:00
|
|
|
|
* @ngdoc service
|
|
|
|
|
|
* @name angular.module.ng.$q
|
|
|
|
|
|
* @requires $rootScope
|
2011-11-08 09:56:42 +00:00
|
|
|
|
*
|
2011-12-12 16:07:39 +00:00
|
|
|
|
* @description
|
|
|
|
|
|
* A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
|
|
|
|
|
|
*
|
|
|
|
|
|
* [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 programing what `try`, `catch` and `throw` keywords are to synchronous programing.
|
|
|
|
|
|
*
|
|
|
|
|
|
* <pre>
|
|
|
|
|
|
* // for the purpose of this example let's assume that variables `$q` and `scope` are
|
|
|
|
|
|
* // available in the current lexical scope (they could have been injected or passed in).
|
|
|
|
|
|
*
|
|
|
|
|
|
* function asyncGreet(name) {
|
|
|
|
|
|
* var deferred = $q.defer();
|
|
|
|
|
|
*
|
|
|
|
|
|
* setTimeout(function() {
|
|
|
|
|
|
* // since this fn executes async in a future turn of the event loop, we need to wrap
|
|
|
|
|
|
* // our code into an $apply call so that the model changes are properly observed.
|
|
|
|
|
|
* scope.$apply(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);
|
|
|
|
|
|
* );
|
|
|
|
|
|
* </pre>
|
|
|
|
|
|
*
|
|
|
|
|
|
* 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 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 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. The callbacks are called with a single argument the result or rejection reason.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This method *returns a new promise* which is resolved or rejected via the return value of the
|
|
|
|
|
|
* `successCallback` or `errorCallback`.
|
|
|
|
|
|
*
|
|
|
|
|
|
*
|
|
|
|
|
|
* # Chaining promises
|
|
|
|
|
|
*
|
|
|
|
|
|
* Because calling `then` api of a promise returns a new derived promise, it is easily possible
|
|
|
|
|
|
* to create a chain of promises:
|
|
|
|
|
|
*
|
|
|
|
|
|
* <pre>
|
|
|
|
|
|
* promiseB = promiseA.then(function(result) {
|
|
|
|
|
|
* return result + 1;
|
|
|
|
|
|
* });
|
|
|
|
|
|
*
|
|
|
|
|
|
* // promiseB will be resolved immediately after promiseA is resolved and it's value will be
|
|
|
|
|
|
* // the result of promiseA incremented by 1
|
|
|
|
|
|
* </pre>
|
|
|
|
|
|
*
|
|
|
|
|
|
* 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 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 repaints, which would result in flickering UI.
|
|
|
|
|
|
* - $q promises are recognized 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.
|
2011-11-08 09:56:42 +00:00
|
|
|
|
*/
|
2011-12-12 16:07:39 +00:00
|
|
|
|
function $QProvider() {
|
2011-11-08 09:56:42 +00:00
|
|
|
|
|
2011-12-12 16:07:39 +00:00
|
|
|
|
this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
|
|
|
|
|
|
return qFactory(function(callback) {
|
|
|
|
|
|
$rootScope.$evalAsync(callback);
|
|
|
|
|
|
}, $exceptionHandler);
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
2011-11-08 09:56:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
2011-12-12 16:07:39 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 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.
|
|
|
|
|
|
* @returns {object} Promise manager.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function qFactory(nextTick, exceptionHandler) {
|
2011-12-09 17:18:12 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @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.
|
|
|
|
|
|
*/
|
2011-11-08 09:56:42 +00:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-12-09 17:18:12 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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`.
|
|
|
|
|
|
*
|
|
|
|
|
|
* <pre>
|
|
|
|
|
|
* 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);
|
|
|
|
|
|
* });
|
|
|
|
|
|
* </pre>
|
|
|
|
|
|
*
|
|
|
|
|
|
* @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`.
|
|
|
|
|
|
*/
|
2011-11-08 09:56:42 +00:00
|
|
|
|
var reject = function(reason) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
then: function(callback, errback) {
|
|
|
|
|
|
var result = defer();
|
|
|
|
|
|
nextTick(function() {
|
|
|
|
|
|
result.resolve(errback(reason));
|
|
|
|
|
|
});
|
|
|
|
|
|
return result.promise;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-12-09 17:18:12 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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.
|
|
|
|
|
|
*/
|
2011-11-08 09:56:42 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-12-09 17:18:12 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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.<Promise>} promises An array of promises.
|
|
|
|
|
|
* @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.
|
|
|
|
|
|
*/
|
2011-11-08 09:56:42 +00:00
|
|
|
|
function all(promises) {
|
|
|
|
|
|
var deferred = defer(),
|
|
|
|
|
|
counter = promises.length,
|
|
|
|
|
|
results = [];
|
|
|
|
|
|
|
|
|
|
|
|
forEach(promises, function(promise, index) {
|
|
|
|
|
|
promise.then(function(value) {
|
|
|
|
|
|
if (index in results) return;
|
|
|
|
|
|
results[index] = value;
|
|
|
|
|
|
if (!(--counter)) deferred.resolve(results);
|
|
|
|
|
|
}, function(reason) {
|
|
|
|
|
|
if (index in results) return;
|
|
|
|
|
|
deferred.reject(reason);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return deferred.promise;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
defer: defer,
|
|
|
|
|
|
reject: reject,
|
|
|
|
|
|
when: when,
|
|
|
|
|
|
all: all
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|