aboutsummaryrefslogtreecommitdiffstats
path: root/src/Deferred.js
diff options
context:
space:
mode:
authorIgor Minar2011-11-08 01:56:42 -0800
committerIgor Minar2011-11-30 14:49:03 -0500
commit1cdfa3b9601c199ec0b45096b38e26350eca744f (patch)
treec6b87d6a8cdbcd7e65a1bbad304df25e993b8568 /src/Deferred.js
parent16363d8000a484b428a16eb07d8bd0f19c4b4337 (diff)
downloadangular.js-1cdfa3b9601c199ec0b45096b38e26350eca744f.tar.bz2
feat(deferreds/promises): Q-like deferred/promise implementation with a ton of specs
Diffstat (limited to 'src/Deferred.js')
-rw-r--r--src/Deferred.js206
1 files changed, 206 insertions, 0 deletions
diff --git a/src/Deferred.js b/src/Deferred.js
new file mode 100644
index 00000000..bf20d413
--- /dev/null
+++ b/src/Deferred.js
@@ -0,0 +1,206 @@
+'use strict';
+
+/**
+ * inspired by Kris Kowal's Q (https://github.com/kriskowal/q)
+ */
+
+/**
+ * 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);
+ }
+ }
+
+ 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;
+ }
+ };
+ };
+
+
+ var reject = function(reason) {
+ return {
+ then: function(callback, errback) {
+ var result = defer();
+ nextTick(function() {
+ result.resolve(errback(reason));
+ });
+ return result.promise;
+ }
+ };
+ };
+
+
+ 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);
+ }
+
+
+ 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
+ };
+}
+
+// TODO(i): move elsewhere
+function $QProvider() {
+
+ this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
+ return qFactory(function(callback) {
+ $rootScope.$evalAsync(callback);
+ }, $exceptionHandler);
+ }];
+}