From 9632f5c1c73c2628121d49aa2d6868fc5d8aef1a Mon Sep 17 00:00:00 2001
From: Igor Minar
Date: Mon, 12 Dec 2011 08:08:38 -0800
Subject: style(q): rename src/Deferred.js to src/service/q.js
---
angularFiles.js | 2 +-
src/Deferred.js | 387 ------------------------
src/service/q.js | 387 ++++++++++++++++++++++++
test/DeferredSpec.js | 823 --------------------------------------------------
test/service/qSpec.js | 823 ++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 1211 insertions(+), 1211 deletions(-)
delete mode 100644 src/Deferred.js
create mode 100644 src/service/q.js
delete mode 100644 test/DeferredSpec.js
create mode 100644 test/service/qSpec.js
diff --git a/angularFiles.js b/angularFiles.js
index 0f0b4509..8d35764b 100644
--- a/angularFiles.js
+++ b/angularFiles.js
@@ -9,7 +9,6 @@ angularFiles = {
'src/jqLite.js',
'src/apis.js',
'src/service/autoScroll.js',
- 'src/Deferred.js',
'src/service/browser.js',
'src/service/cacheFactory.js',
'src/service/compiler.js',
@@ -29,6 +28,7 @@ angularFiles = {
'src/service/log.js',
'src/service/resource.js',
'src/service/parse.js',
+ 'src/service/q.js',
'src/service/route.js',
'src/service/routeParams.js',
'src/service/scope.js',
diff --git a/src/Deferred.js b/src/Deferred.js
deleted file mode 100644
index f0c030b0..00000000
--- a/src/Deferred.js
+++ /dev/null
@@ -1,387 +0,0 @@
-'use strict';
-
-/**
- * @ngdoc service
- * @name angular.module.ng.$q
- * @requires $rootScope
- *
- * @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.
- *
- *
- * // 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);
- * );
- *
- *
- * 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:
- *
- *
- * 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
- *
- *
- * 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.
- */
-function $QProvider() {
-
- this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
- return qFactory(function(callback) {
- $rootScope.$evalAsync(callback);
- }, $exceptionHandler);
- }];
-}
-
-
-/**
- * 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) {
-
- /**
- * @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.} 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.
- */
- 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
- };
-}
diff --git a/src/service/q.js b/src/service/q.js
new file mode 100644
index 00000000..f0c030b0
--- /dev/null
+++ b/src/service/q.js
@@ -0,0 +1,387 @@
+'use strict';
+
+/**
+ * @ngdoc service
+ * @name angular.module.ng.$q
+ * @requires $rootScope
+ *
+ * @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.
+ *
+ *
+ * // 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);
+ * );
+ *
+ *
+ * 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:
+ *
+ *
+ * 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
+ *
+ *
+ * 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.
+ */
+function $QProvider() {
+
+ this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
+ return qFactory(function(callback) {
+ $rootScope.$evalAsync(callback);
+ }, $exceptionHandler);
+ }];
+}
+
+
+/**
+ * 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) {
+
+ /**
+ * @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.} 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.
+ */
+ 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
+ };
+}
diff --git a/test/DeferredSpec.js b/test/DeferredSpec.js
deleted file mode 100644
index e592ab87..00000000
--- a/test/DeferredSpec.js
+++ /dev/null
@@ -1,823 +0,0 @@
-'use strict';
-
-/**
- http://wiki.commonjs.org/wiki/Promises
- http://www.slideshare.net/domenicdenicola/callbacks-promises-and-coroutines-oh-my-the-evolution-of-asynchronicity-in-javascript
-
- Q: https://github.com/kriskowal/q
- https://github.com/kriskowal/q/blob/master/design/README.js
- https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md
- http://jsconf.eu/2010/speaker/commonjs_i_promise_by_kris_kow.html
- - good walkthrough of the Q api's and design, jump to 15:30
-
- twisted: http://twistedmatrix.com/documents/11.0.0/api/twisted.internet.defer.Deferred.html
- dojo: https://github.com/dojo/dojo/blob/master/_base/Deferred.js
- http://dojotoolkit.org/api/1.6/dojo/Deferred
- http://dojotoolkit.org/documentation/tutorials/1.6/promises/
- when.js: https://github.com/briancavalier/when.js
- DART: http://www.dartlang.org/docs/api/Promise.html#Promise::Promise
- http://code.google.com/p/dart/source/browse/trunk/dart/corelib/src/promise.dart
- http://codereview.chromium.org/8271014/patch/11003/12005
- https://chromereviews.googleplex.com/3365018/
- WinJS: http://msdn.microsoft.com/en-us/library/windows/apps/br211867.aspx
-
- http://download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Future.html
- http://en.wikipedia.org/wiki/Futures_and_promises
- http://wiki.ecmascript.org/doku.php?id=strawman:deferred_functions
- http://wiki.ecmascript.org/doku.php?id=strawman:async_functions
-
-
- http://jsperf.com/throw-vs-return
-*/
-
-describe('q', function() {
- var q, defer, deferred, promise, log;
-
- /**
- * Creates a callback that logs its invocation in `log`.
- *
- * @param {(number|string)} name Suffix for 'success' name. e.g. success(1) => success1
- * @param {*=} returnVal Value that the callback should return. If unspecified, the passed in
- * value is returned.
- * @param {boolean=} throwReturnVal If true, the `returnVal` will be thrown rather than returned.
- */
- function success(name, returnVal, throwReturnVal) {
- var returnValDefined = (arguments.length >= 2);
-
- return function() {
- name = 'success' + (name || '');
- var args = toJson(sliceArgs(arguments)).replace(/(^\[|"|\]$)/g, '');
- log.push(name + '(' + args + ')');
- returnVal = returnValDefined ? returnVal : arguments[0];
- if (throwReturnVal) throw returnVal;
- return returnVal;
- }
- }
-
-
- /**
- * Creates a callback that logs its invocation in `log`.
- *
- * @param {(number|string)} name Suffix for 'error' name. e.g. error(1) => error1
- * @param {*=} returnVal Value that the callback should return. If unspecified, the passed in
- * value is rethrown.
- * @param {boolean=} throwReturnVal If true, the `returnVal` will be thrown rather than returned.
- */
- function error(name, returnVal, throwReturnVal) {
- var returnValDefined = (arguments.length >= 2);
-
- return function(){
- name = 'error' + (name || '');
- log.push(name + '(' + [].join.call(arguments, ',') + ')');
- returnVal = returnValDefined ? returnVal : q.reject(arguments[0]);
- if (throwReturnVal) throw returnVal;
- return returnVal;
- }
- }
-
-
- /** helper for synchronous resolution of deferred */
- function syncResolve(deferred, result) {
- deferred.resolve(result);
- mockNextTick.flush();
- }
-
-
- /** helper for synchronous rejection of deferred */
- function syncReject(deferred, reason) {
- deferred.reject(reason);
- mockNextTick.flush();
- }
-
-
- /** converts the `log` to a '; '-separated string */
- function logStr() {
- return log.join('; ');
- }
-
-
- var mockNextTick = {
- nextTick: function(task) {
- mockNextTick.queue.push(task);
- },
- queue: [],
- flush: function() {
- if (!mockNextTick.queue.length) throw new Error('Nothing to be flushed!');
- while (mockNextTick.queue.length) {
- var queue = mockNextTick.queue;
- mockNextTick.queue = [];
- forEach(queue, function(task) {
- try {
- task();
- } catch(e) {
- dump('exception in mockNextTick:', e, e.name, e.message, task);
- }
- });
- }
- }
- }
-
-
- beforeEach(function() {
- q = qFactory(mockNextTick.nextTick, noop),
- defer = q.defer;
- deferred = defer()
- promise = deferred.promise;
- log = [];
- mockNextTick.queue = [];
- });
-
-
- afterEach(function() {
- expect(mockNextTick.queue.length).toBe(0);
- });
-
-
- describe('defer', function() {
- it('should create a new deferred', function() {
- expect(deferred.promise).toBeDefined();
- expect(deferred.resolve).toBeDefined();
- expect(deferred.reject).toBeDefined();
- });
-
-
- describe('resolve', function() {
- it('should fulfill the promise and execute all success callbacks in the registration order',
- function() {
- promise.then(success(1), error());
- promise.then(success(2), error());
- expect(logStr()).toBe('');
-
- deferred.resolve('foo');
- mockNextTick.flush();
- expect(logStr()).toBe('success1(foo); success2(foo)');
- });
-
-
- it('should do nothing if a promise was previously resolved', function() {
- promise.then(success(), error());
- expect(logStr()).toBe('');
-
- deferred.resolve('foo');
- mockNextTick.flush();
- expect(logStr()).toBe('success(foo)');
-
- log = [];
- deferred.resolve('bar');
- deferred.reject('baz');
- expect(mockNextTick.queue.length).toBe(0);
- expect(logStr()).toBe('');
- });
-
-
- it('should do nothing if a promise was previously rejected', function() {
- promise.then(success(), error());
- expect(logStr()).toBe('');
-
- deferred.reject('foo');
- mockNextTick.flush();
- expect(logStr()).toBe('error(foo)');
-
- log = [];
- deferred.resolve('bar');
- deferred.reject('baz');
- expect(mockNextTick.queue.length).toBe(0);
- expect(logStr()).toBe('');
- });
-
-
- it('should allow deferred resolution with a new promise', function() {
- var deferred2 = defer();
- promise.then(success(), error());
-
- deferred.resolve(deferred2.promise);
- mockNextTick.flush();
- expect(logStr()).toBe('');
-
- deferred2.resolve('foo');
- mockNextTick.flush();
- expect(logStr()).toBe('success(foo)');
- });
-
-
- it('should call the callback in the next turn', function() {
- promise.then(success());
- expect(logStr()).toBe('');
-
- deferred.resolve('foo');
- expect(logStr()).toBe('');
-
- mockNextTick.flush();
- expect(logStr()).toBe('success(foo)');
- });
-
-
- it('should support non-bound execution', function() {
- var resolver = deferred.resolve;
- promise.then(success(), error());
- resolver('detached');
- mockNextTick.flush();
- expect(logStr()).toBe('success(detached)');
- });
-
-
- it('should not break if a callbacks registers another callback', function() {
- promise.then(function() {
- log.push('outer');
- promise.then(function() {
- log.push('inner');
- });
- });
-
- deferred.resolve('foo');
- expect(logStr()).toBe('');
-
- mockNextTick.flush();
- expect(logStr()).toBe('outer; inner');
- });
-
-
- it('should not break if a callbacks tries to resolve the deferred again', function() {
- promise.then(function(val) {
- log.push('success1(' + val + ')');
- deferred.resolve('bar');
- });
-
- promise.then(success(2));
-
- deferred.resolve('foo');
- expect(logStr()).toBe('');
-
- mockNextTick.flush();
- expect(logStr()).toBe('success1(foo); success2(foo)');
- });
- });
-
-
- describe('reject', function() {
- it('should reject the promise and execute all error callbacks in the registration order',
- function() {
- promise.then(success(), error(1));
- promise.then(success(), error(2));
- expect(logStr()).toBe('');
-
- deferred.reject('foo');
- mockNextTick.flush();
- expect(logStr()).toBe('error1(foo); error2(foo)');
- });
-
-
- it('should do nothing if a promise was previously resolved', function() {
- promise.then(success(1), error(1));
- expect(logStr()).toBe('');
-
- deferred.resolve('foo');
- mockNextTick.flush();
- expect(logStr()).toBe('success1(foo)');
-
- log = [];
- deferred.reject('bar');
- deferred.resolve('baz');
- expect(mockNextTick.queue.length).toBe(0);
- expect(logStr()).toBe('');
-
- promise.then(success(2), error(2))
- expect(logStr()).toBe('');
- mockNextTick.flush();
- expect(logStr()).toBe('success2(foo)');
- });
-
-
- it('should do nothing if a promise was previously rejected', function() {
- promise.then(success(1), error(1));
- expect(logStr()).toBe('');
-
- deferred.reject('foo');
- mockNextTick.flush();
- expect(logStr()).toBe('error1(foo)');
-
- log = [];
- deferred.reject('bar');
- deferred.resolve('baz');
- expect(mockNextTick.queue.length).toBe(0);
- expect(logStr()).toBe('');
-
- promise.then(success(2), error(2))
- expect(logStr()).toBe('');
- mockNextTick.flush();
- expect(logStr()).toBe('error2(foo)');
- });
-
-
- it('should not defer rejection with a new promise', function() {
- var deferred2 = defer();
- promise.then(success(), error());
-
- deferred.reject(deferred2.promise);
- mockNextTick.flush();
- expect(logStr()).toBe('error([object Object])');
- });
-
-
- it('should call the error callback in the next turn', function() {
- promise.then(success(), error());
- expect(logStr()).toBe('');
-
- deferred.reject('foo');
- expect(logStr()).toBe('');
-
- mockNextTick.flush();
- expect(logStr()).toBe('error(foo)');
- });
-
-
- it('should support non-bound execution', function() {
- var rejector = deferred.reject;
- promise.then(success(), error());
- rejector('detached');
- mockNextTick.flush();
- expect(logStr()).toBe('error(detached)');
- });
- });
-
-
- describe('promise', function() {
- it('should have a then method', function() {
- expect(typeof promise.then).toBe('function');
- });
-
-
- describe('then', function() {
- it('should allow registration of a success callback without an errback and resolve',
- function() {
- promise.then(success());
- syncResolve(deferred, 'foo');
- expect(logStr()).toBe('success(foo)');
- });
-
- it('should allow registration of a success callback without an errback and reject',
- function() {
- promise.then(success());
- syncReject(deferred, 'foo');
- expect(logStr()).toBe('');
- });
-
-
- it('should allow registration of an errback without a success callback and reject',
- function() {
- promise.then(null, error());
- syncReject(deferred, 'oops!');
- expect(logStr()).toBe('error(oops!)');
- });
-
-
- it('should allow registration of an errback without a success callback and resolve',
- function() {
- promise.then(null, error());
- syncResolve(deferred, 'done');
- expect(logStr()).toBe('');
- });
-
-
- it('should resolve all callbacks with the original value', function() {
- promise.then(success('A', 'aVal'), error());
- promise.then(success('B', 'bErr', true), error());
- promise.then(success('C', q.reject('cReason')), error());
- promise.then(success('D', 'dVal'), error());
-
- expect(logStr()).toBe('');
- syncResolve(deferred, 'yup');
- expect(logStr()).toBe('successA(yup); successB(yup); successC(yup); successD(yup)');
- });
-
-
- it('should reject all callbacks with the original reason', function() {
- promise.then(success(), error('A', 'aVal'));
- promise.then(success(), error('B', 'bEr', true));
- promise.then(success(), error('C', q.reject('cReason')));
- promise.then(success(), error('D', 'dVal'));
-
- expect(logStr()).toBe('');
- syncReject(deferred, 'noo!');
- expect(logStr()).toBe('errorA(noo!); errorB(noo!); errorC(noo!); errorD(noo!)');
- });
-
-
- it('should propagate resolution and rejection between dependent promises', function() {
- promise.then(success(1, 'x'), error('1')).
- then(success(2, 'y', true), error('2')).
- then(success(3), error(3, 'z', true)).
- then(success(4), error(4, 'done')).
- then(success(5), error(5));
-
- expect(logStr()).toBe('');
- syncResolve(deferred, 'sweet!');
- expect(log).toEqual(['success1(sweet!)',
- 'success2(x)',
- 'error3(y)',
- 'error4(z)',
- 'success5(done)']);
- });
-
-
- it('should reject a derived promise if an exception is thrown while resolving its parent',
- function() {
- promise.then(success(1, 'oops', true)).
- then(success(2), error(2));
- syncResolve(deferred, 'done!');
- expect(logStr()).toBe('success1(done!); error2(oops)');
- });
-
-
- it('should reject a derived promise if an exception is thrown while rejecting its parent',
- function() {
- promise.then(null, error(1, 'oops', true)).
- then(success(2), error(2));
- syncReject(deferred, 'timeout');
- expect(logStr()).toBe('error1(timeout); error2(oops)');
- });
-
-
- it('should call success callback in the next turn even if promise is already resolved',
- function() {
- deferred.resolve('done!');
-
- promise.then(success());
- expect(logStr()).toBe('');
-
- mockNextTick.flush();
- expect(log).toEqual(['success(done!)']);
- });
-
-
- it('should call errpr callback in the next turn even if promise is already rejected',
- function() {
- deferred.reject('oops!');
-
- promise.then(null, error());
- expect(logStr()).toBe('');
-
- mockNextTick.flush();
- expect(log).toEqual(['error(oops!)']);
- });
- });
- });
- });
-
-
- describe('reject', function() {
- it('should package a string into a rejected promise', function() {
- var rejectedPromise = q.reject('not gonna happen');
- promise.then(success(), error());
- syncResolve(deferred, rejectedPromise);
- expect(log).toEqual(['error(not gonna happen)']);
- });
-
-
- it('should package an exception into a rejected promise', function() {
- var rejectedPromise = q.reject(Error('not gonna happen'));
- promise.then(success(), error());
- syncResolve(deferred, rejectedPromise);
- expect(log).toEqual(['error(Error: not gonna happen)']);
- });
- });
-
-
- describe('when', function() {
- describe('resolution', function() {
- it('should call the success callback in the next turn when the value is a non-promise',
- function() {
- q.when('hello', success(), error());
- expect(logStr()).toBe('');
- mockNextTick.flush();
- expect(logStr()).toBe('success(hello)');
- });
-
-
- it('should call the success callback in the next turn when the value is a resolved promise',
- function() {
- deferred.resolve('hello');
- q.when(deferred.promise, success(), error());
- expect(logStr()).toBe('');
- mockNextTick.flush();
- expect(logStr()).toBe('success(hello)');
- });
-
-
- it('should call the errback in the next turn when the value is a rejected promise', function() {
- deferred.reject('nope');
- q.when(deferred.promise, success(), error());
- expect(logStr()).toBe('');
- mockNextTick.flush();
- expect(logStr()).toBe('error(nope)');
- });
-
-
- it('should call the success callback after the original promise is resolved',
- function() {
- q.when(deferred.promise, success(), error());
- expect(logStr()).toBe('');
- mockNextTick.flush();
- expect(logStr()).toBe('');
- syncResolve(deferred, 'hello');
- expect(logStr()).toBe('success(hello)');
- });
-
-
- it('should call the errback after the orignal promise is rejected',
- function() {
- q.when(deferred.promise, success(), error());
- expect(logStr()).toBe('');
- mockNextTick.flush();
- expect(logStr()).toBe('');
- syncReject(deferred, 'nope');
- expect(logStr()).toBe('error(nope)');
- });
- });
-
-
- describe('optional callbacks', function() {
- it('should not require success callback and propagate resolution', function() {
- q.when('hi', null, error()).then(success(2), error());
- expect(logStr()).toBe('');
- mockNextTick.flush();
- expect(logStr()).toBe('success2(hi)');
- });
-
-
- it('should not require success callback and propagate rejection', function() {
- q.when(q.reject('sorry'), null, error(1)).then(success(), error(2));
- expect(logStr()).toBe('');
- mockNextTick.flush();
- expect(logStr()).toBe('error1(sorry); error2(sorry)');
- });
-
-
- it('should not require errback and propagate resolution', function() {
- q.when('hi', success(1, 'hello')).then(success(2), error());
- expect(logStr()).toBe('');
- mockNextTick.flush();
- expect(logStr()).toBe('success1(hi); success2(hello)');
- });
-
-
- it('should not require errback and propagate rejection', function() {
- q.when(q.reject('sorry'), success()).then(success(2), error(2));
- expect(logStr()).toBe('');
- mockNextTick.flush();
- expect(logStr()).toBe('error2(sorry)');
- });
- });
-
-
- describe('returned promise', function() {
- it('should return a promise that can be resolved with a value returned from the success ' +
- 'callback', function() {
- q.when('hello', success(1, 'hi'), error()).then(success(2), error());
- mockNextTick.flush();
- expect(logStr()).toBe('success1(hello); success2(hi)');
- });
-
-
- it('should return a promise that can be rejected with a rejected promise returned from the ' +
- 'success callback', function() {
- q.when('hello', success(1, q.reject('sorry')), error()).then(success(), error(2));
- mockNextTick.flush();
- expect(logStr()).toBe('success1(hello); error2(sorry)');
- });
-
-
- it('should return a promise that can be resolved with a value returned from the errback',
- function() {
- q.when(q.reject('sorry'), success(), error(1, 'hi')).then(success(2), error());
- mockNextTick.flush();
- expect(logStr()).toBe('error1(sorry); success2(hi)');
- });
-
-
- it('should return a promise that can be rejected with a rejected promise returned from the ' +
- 'errback', function() {
- q.when(q.reject('sorry'), success(), error(1, q.reject('sigh'))).then(success(), error(2));
- mockNextTick.flush();
- expect(logStr()).toBe('error1(sorry); error2(sigh)');
- });
-
-
- it('should return a promise that can be resolved with a promise returned from the success ' +
- 'callback', function() {
- var deferred2 = defer();
- q.when('hi', success(1, deferred2.promise), error()).then(success(2), error());
- mockNextTick.flush();
- expect(logStr()).toBe('success1(hi)');
- syncResolve(deferred2, 'finally!');
- expect(logStr()).toBe('success1(hi); success2(finally!)');
- });
-
-
- it('should return a promise that can be resolved with promise returned from the errback ' +
- 'callback', function() {
- var deferred2 = defer();
- q.when(q.reject('sorry'), success(), error(1, deferred2.promise)).then(success(2), error());
- mockNextTick.flush();
- expect(logStr()).toBe('error1(sorry)');
- syncResolve(deferred2, 'finally!');
- expect(logStr()).toBe('error1(sorry); success2(finally!)');
- });
- });
-
-
- describe('security', function() {
- it('should call success callback only once even if the original promise gets fullfilled ' +
- 'multiple times', function() {
- var evilPromise = {
- then: function(success, error) {
- evilPromise.success = success;
- evilPromise.error = error;
- }
- }
-
- q.when(evilPromise, success(), error());
- mockNextTick.flush();
- expect(logStr()).toBe('');
- evilPromise.success('done');
- mockNextTick.flush(); // TODO(i) wrong queue, evil promise would be resolved outside of the
- // scope.$apply lifecycle and in that case we should have some kind
- // of fallback queue for calling our callbacks from. Otherwise the
- // application will get stuck until something triggers next $apply.
- expect(logStr()).toBe('success(done)');
-
- evilPromise.success('evil is me');
- evilPromise.error('burn burn');
- expect(logStr()).toBe('success(done)');
- });
-
-
- it('should call errback only once even if the original promise gets fullfilled multiple ' +
- 'times', function() {
- var evilPromise = {
- then: function(success, error) {
- evilPromise.success = success;
- evilPromise.error = error;
- }
- }
-
- q.when(evilPromise, success(), error());
- mockNextTick.flush();
- expect(logStr()).toBe('');
- evilPromise.error('failed');
- expect(logStr()).toBe('error(failed)');
-
- evilPromise.error('muhaha');
- evilPromise.success('take this');
- expect(logStr()).toBe('error(failed)');
- });
- });
- });
-
-
- describe('all', function() {
- it('should take an array of promises and return a promise for an array of results', function() {
- var deferred1 = defer(),
- deferred2 = defer();
-
- q.all([promise, deferred1.promise, deferred2.promise]).then(success(), error());
- expect(logStr()).toBe('');
- syncResolve(deferred, 'hi');
- expect(logStr()).toBe('');
- syncResolve(deferred2, 'cau');
- expect(logStr()).toBe('');
- syncResolve(deferred1, 'hola');
- expect(logStr()).toBe('success([hi,hola,cau])');
- });
-
-
- it('should reject the derived promise if at least one of the promises in the array is rejected',
- function() {
- var deferred1 = defer(),
- deferred2 = defer();
-
- q.all([promise, deferred1.promise, deferred2.promise]).then(success(), error());
- expect(logStr()).toBe('');
- syncResolve(deferred2, 'cau');
- expect(logStr()).toBe('');
- syncReject(deferred1, 'oops');
- expect(logStr()).toBe('error(oops)');
- });
-
-
- it('should ignore multiple resolutions of an (evil) array promise', function() {
- var evilPromise = {
- then: function(success, error) {
- evilPromise.success = success;
- evilPromise.error = error;
- }
- }
-
- q.all([promise, evilPromise]).then(success(), error());
- expect(logStr()).toBe('');
-
- evilPromise.success('first');
- evilPromise.success('muhaha');
- evilPromise.error('arghhh');
- expect(logStr()).toBe('');
-
- syncResolve(deferred, 'done');
- expect(logStr()).toBe('success([done,first])');
- });
- });
-
-
- describe('exception logging', function() {
- var mockExceptionLogger = {
- log: [],
- logger: function(e) {
- mockExceptionLogger.log.push(e);
- }
- }
-
-
- beforeEach(function() {
- q = qFactory(mockNextTick.nextTick, mockExceptionLogger.logger),
- defer = q.defer;
- deferred = defer()
- promise = deferred.promise;
- log = [];
- mockExceptionLogger.log = [];
- });
-
-
- describe('in then', function() {
- it('should log exceptions thrown in a success callback and reject the derived promise',
- function() {
- var success1 = success(1, 'oops', true);
- promise.then(success1).then(success(2), error(2));
- syncResolve(deferred, 'done');
- expect(logStr()).toBe('success1(done); error2(oops)');
- expect(mockExceptionLogger.log).toEqual(['oops']);
- });
-
-
- it('should NOT log exceptions when a success callback returns rejected promise', function() {
- promise.then(success(1, q.reject('rejected'))).then(success(2), error(2));
- syncResolve(deferred, 'done');
- expect(logStr()).toBe('success1(done); error2(rejected)');
- expect(mockExceptionLogger.log).toEqual([]);
- });
-
-
- it('should log exceptions thrown in a errback and reject the derived promise', function() {
- var error1 = error(1, 'oops', true);
- promise.then(null, error1).then(success(2), error(2));
- syncReject(deferred, 'nope');
- expect(logStr()).toBe('error1(nope); error2(oops)');
- expect(mockExceptionLogger.log).toEqual(['oops']);
- });
-
-
- it('should NOT log exceptions when an errback returns a rejected promise', function() {
- promise.then(null, error(1, q.reject('rejected'))).then(success(2), error(2));
- syncReject(deferred, 'nope');
- expect(logStr()).toBe('error1(nope); error2(rejected)');
- expect(mockExceptionLogger.log).toEqual([]);
- });
- });
-
-
- describe('in when', function() {
- it('should log exceptions thrown in a success callback and reject the derived promise',
- function() {
- var success1 = success(1, 'oops', true);
- q.when('hi', success1, error()).then(success(), error(2));
- mockNextTick.flush();
- expect(logStr()).toBe('success1(hi); error2(oops)');
- expect(mockExceptionLogger.log).toEqual(['oops']);
- });
-
-
- it('should NOT log exceptions when a success callback returns rejected promise', function() {
- q.when('hi', success(1, q.reject('rejected'))).then(success(2), error(2));
- mockNextTick.flush();
- expect(logStr()).toBe('success1(hi); error2(rejected)');
- expect(mockExceptionLogger.log).toEqual([]);
- });
-
-
- it('should log exceptions thrown in a errback and reject the derived promise', function() {
- var error1 = error(1, 'oops', true);
- q.when(q.reject('sorry'), success(), error1).then(success(), error(2));
- mockNextTick.flush();
- expect(logStr()).toBe('error1(sorry); error2(oops)');
- expect(mockExceptionLogger.log).toEqual(['oops']);
- });
-
-
- it('should NOT log exceptions when an errback returns a rejected promise', function() {
- q.when(q.reject('sorry'), success(), error(1, q.reject('rejected'))).
- then(success(2), error(2));
- mockNextTick.flush();
- expect(logStr()).toBe('error1(sorry); error2(rejected)');
- expect(mockExceptionLogger.log).toEqual([]);
- });
- });
- });
-});
diff --git a/test/service/qSpec.js b/test/service/qSpec.js
new file mode 100644
index 00000000..e592ab87
--- /dev/null
+++ b/test/service/qSpec.js
@@ -0,0 +1,823 @@
+'use strict';
+
+/**
+ http://wiki.commonjs.org/wiki/Promises
+ http://www.slideshare.net/domenicdenicola/callbacks-promises-and-coroutines-oh-my-the-evolution-of-asynchronicity-in-javascript
+
+ Q: https://github.com/kriskowal/q
+ https://github.com/kriskowal/q/blob/master/design/README.js
+ https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md
+ http://jsconf.eu/2010/speaker/commonjs_i_promise_by_kris_kow.html
+ - good walkthrough of the Q api's and design, jump to 15:30
+
+ twisted: http://twistedmatrix.com/documents/11.0.0/api/twisted.internet.defer.Deferred.html
+ dojo: https://github.com/dojo/dojo/blob/master/_base/Deferred.js
+ http://dojotoolkit.org/api/1.6/dojo/Deferred
+ http://dojotoolkit.org/documentation/tutorials/1.6/promises/
+ when.js: https://github.com/briancavalier/when.js
+ DART: http://www.dartlang.org/docs/api/Promise.html#Promise::Promise
+ http://code.google.com/p/dart/source/browse/trunk/dart/corelib/src/promise.dart
+ http://codereview.chromium.org/8271014/patch/11003/12005
+ https://chromereviews.googleplex.com/3365018/
+ WinJS: http://msdn.microsoft.com/en-us/library/windows/apps/br211867.aspx
+
+ http://download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Future.html
+ http://en.wikipedia.org/wiki/Futures_and_promises
+ http://wiki.ecmascript.org/doku.php?id=strawman:deferred_functions
+ http://wiki.ecmascript.org/doku.php?id=strawman:async_functions
+
+
+ http://jsperf.com/throw-vs-return
+*/
+
+describe('q', function() {
+ var q, defer, deferred, promise, log;
+
+ /**
+ * Creates a callback that logs its invocation in `log`.
+ *
+ * @param {(number|string)} name Suffix for 'success' name. e.g. success(1) => success1
+ * @param {*=} returnVal Value that the callback should return. If unspecified, the passed in
+ * value is returned.
+ * @param {boolean=} throwReturnVal If true, the `returnVal` will be thrown rather than returned.
+ */
+ function success(name, returnVal, throwReturnVal) {
+ var returnValDefined = (arguments.length >= 2);
+
+ return function() {
+ name = 'success' + (name || '');
+ var args = toJson(sliceArgs(arguments)).replace(/(^\[|"|\]$)/g, '');
+ log.push(name + '(' + args + ')');
+ returnVal = returnValDefined ? returnVal : arguments[0];
+ if (throwReturnVal) throw returnVal;
+ return returnVal;
+ }
+ }
+
+
+ /**
+ * Creates a callback that logs its invocation in `log`.
+ *
+ * @param {(number|string)} name Suffix for 'error' name. e.g. error(1) => error1
+ * @param {*=} returnVal Value that the callback should return. If unspecified, the passed in
+ * value is rethrown.
+ * @param {boolean=} throwReturnVal If true, the `returnVal` will be thrown rather than returned.
+ */
+ function error(name, returnVal, throwReturnVal) {
+ var returnValDefined = (arguments.length >= 2);
+
+ return function(){
+ name = 'error' + (name || '');
+ log.push(name + '(' + [].join.call(arguments, ',') + ')');
+ returnVal = returnValDefined ? returnVal : q.reject(arguments[0]);
+ if (throwReturnVal) throw returnVal;
+ return returnVal;
+ }
+ }
+
+
+ /** helper for synchronous resolution of deferred */
+ function syncResolve(deferred, result) {
+ deferred.resolve(result);
+ mockNextTick.flush();
+ }
+
+
+ /** helper for synchronous rejection of deferred */
+ function syncReject(deferred, reason) {
+ deferred.reject(reason);
+ mockNextTick.flush();
+ }
+
+
+ /** converts the `log` to a '; '-separated string */
+ function logStr() {
+ return log.join('; ');
+ }
+
+
+ var mockNextTick = {
+ nextTick: function(task) {
+ mockNextTick.queue.push(task);
+ },
+ queue: [],
+ flush: function() {
+ if (!mockNextTick.queue.length) throw new Error('Nothing to be flushed!');
+ while (mockNextTick.queue.length) {
+ var queue = mockNextTick.queue;
+ mockNextTick.queue = [];
+ forEach(queue, function(task) {
+ try {
+ task();
+ } catch(e) {
+ dump('exception in mockNextTick:', e, e.name, e.message, task);
+ }
+ });
+ }
+ }
+ }
+
+
+ beforeEach(function() {
+ q = qFactory(mockNextTick.nextTick, noop),
+ defer = q.defer;
+ deferred = defer()
+ promise = deferred.promise;
+ log = [];
+ mockNextTick.queue = [];
+ });
+
+
+ afterEach(function() {
+ expect(mockNextTick.queue.length).toBe(0);
+ });
+
+
+ describe('defer', function() {
+ it('should create a new deferred', function() {
+ expect(deferred.promise).toBeDefined();
+ expect(deferred.resolve).toBeDefined();
+ expect(deferred.reject).toBeDefined();
+ });
+
+
+ describe('resolve', function() {
+ it('should fulfill the promise and execute all success callbacks in the registration order',
+ function() {
+ promise.then(success(1), error());
+ promise.then(success(2), error());
+ expect(logStr()).toBe('');
+
+ deferred.resolve('foo');
+ mockNextTick.flush();
+ expect(logStr()).toBe('success1(foo); success2(foo)');
+ });
+
+
+ it('should do nothing if a promise was previously resolved', function() {
+ promise.then(success(), error());
+ expect(logStr()).toBe('');
+
+ deferred.resolve('foo');
+ mockNextTick.flush();
+ expect(logStr()).toBe('success(foo)');
+
+ log = [];
+ deferred.resolve('bar');
+ deferred.reject('baz');
+ expect(mockNextTick.queue.length).toBe(0);
+ expect(logStr()).toBe('');
+ });
+
+
+ it('should do nothing if a promise was previously rejected', function() {
+ promise.then(success(), error());
+ expect(logStr()).toBe('');
+
+ deferred.reject('foo');
+ mockNextTick.flush();
+ expect(logStr()).toBe('error(foo)');
+
+ log = [];
+ deferred.resolve('bar');
+ deferred.reject('baz');
+ expect(mockNextTick.queue.length).toBe(0);
+ expect(logStr()).toBe('');
+ });
+
+
+ it('should allow deferred resolution with a new promise', function() {
+ var deferred2 = defer();
+ promise.then(success(), error());
+
+ deferred.resolve(deferred2.promise);
+ mockNextTick.flush();
+ expect(logStr()).toBe('');
+
+ deferred2.resolve('foo');
+ mockNextTick.flush();
+ expect(logStr()).toBe('success(foo)');
+ });
+
+
+ it('should call the callback in the next turn', function() {
+ promise.then(success());
+ expect(logStr()).toBe('');
+
+ deferred.resolve('foo');
+ expect(logStr()).toBe('');
+
+ mockNextTick.flush();
+ expect(logStr()).toBe('success(foo)');
+ });
+
+
+ it('should support non-bound execution', function() {
+ var resolver = deferred.resolve;
+ promise.then(success(), error());
+ resolver('detached');
+ mockNextTick.flush();
+ expect(logStr()).toBe('success(detached)');
+ });
+
+
+ it('should not break if a callbacks registers another callback', function() {
+ promise.then(function() {
+ log.push('outer');
+ promise.then(function() {
+ log.push('inner');
+ });
+ });
+
+ deferred.resolve('foo');
+ expect(logStr()).toBe('');
+
+ mockNextTick.flush();
+ expect(logStr()).toBe('outer; inner');
+ });
+
+
+ it('should not break if a callbacks tries to resolve the deferred again', function() {
+ promise.then(function(val) {
+ log.push('success1(' + val + ')');
+ deferred.resolve('bar');
+ });
+
+ promise.then(success(2));
+
+ deferred.resolve('foo');
+ expect(logStr()).toBe('');
+
+ mockNextTick.flush();
+ expect(logStr()).toBe('success1(foo); success2(foo)');
+ });
+ });
+
+
+ describe('reject', function() {
+ it('should reject the promise and execute all error callbacks in the registration order',
+ function() {
+ promise.then(success(), error(1));
+ promise.then(success(), error(2));
+ expect(logStr()).toBe('');
+
+ deferred.reject('foo');
+ mockNextTick.flush();
+ expect(logStr()).toBe('error1(foo); error2(foo)');
+ });
+
+
+ it('should do nothing if a promise was previously resolved', function() {
+ promise.then(success(1), error(1));
+ expect(logStr()).toBe('');
+
+ deferred.resolve('foo');
+ mockNextTick.flush();
+ expect(logStr()).toBe('success1(foo)');
+
+ log = [];
+ deferred.reject('bar');
+ deferred.resolve('baz');
+ expect(mockNextTick.queue.length).toBe(0);
+ expect(logStr()).toBe('');
+
+ promise.then(success(2), error(2))
+ expect(logStr()).toBe('');
+ mockNextTick.flush();
+ expect(logStr()).toBe('success2(foo)');
+ });
+
+
+ it('should do nothing if a promise was previously rejected', function() {
+ promise.then(success(1), error(1));
+ expect(logStr()).toBe('');
+
+ deferred.reject('foo');
+ mockNextTick.flush();
+ expect(logStr()).toBe('error1(foo)');
+
+ log = [];
+ deferred.reject('bar');
+ deferred.resolve('baz');
+ expect(mockNextTick.queue.length).toBe(0);
+ expect(logStr()).toBe('');
+
+ promise.then(success(2), error(2))
+ expect(logStr()).toBe('');
+ mockNextTick.flush();
+ expect(logStr()).toBe('error2(foo)');
+ });
+
+
+ it('should not defer rejection with a new promise', function() {
+ var deferred2 = defer();
+ promise.then(success(), error());
+
+ deferred.reject(deferred2.promise);
+ mockNextTick.flush();
+ expect(logStr()).toBe('error([object Object])');
+ });
+
+
+ it('should call the error callback in the next turn', function() {
+ promise.then(success(), error());
+ expect(logStr()).toBe('');
+
+ deferred.reject('foo');
+ expect(logStr()).toBe('');
+
+ mockNextTick.flush();
+ expect(logStr()).toBe('error(foo)');
+ });
+
+
+ it('should support non-bound execution', function() {
+ var rejector = deferred.reject;
+ promise.then(success(), error());
+ rejector('detached');
+ mockNextTick.flush();
+ expect(logStr()).toBe('error(detached)');
+ });
+ });
+
+
+ describe('promise', function() {
+ it('should have a then method', function() {
+ expect(typeof promise.then).toBe('function');
+ });
+
+
+ describe('then', function() {
+ it('should allow registration of a success callback without an errback and resolve',
+ function() {
+ promise.then(success());
+ syncResolve(deferred, 'foo');
+ expect(logStr()).toBe('success(foo)');
+ });
+
+ it('should allow registration of a success callback without an errback and reject',
+ function() {
+ promise.then(success());
+ syncReject(deferred, 'foo');
+ expect(logStr()).toBe('');
+ });
+
+
+ it('should allow registration of an errback without a success callback and reject',
+ function() {
+ promise.then(null, error());
+ syncReject(deferred, 'oops!');
+ expect(logStr()).toBe('error(oops!)');
+ });
+
+
+ it('should allow registration of an errback without a success callback and resolve',
+ function() {
+ promise.then(null, error());
+ syncResolve(deferred, 'done');
+ expect(logStr()).toBe('');
+ });
+
+
+ it('should resolve all callbacks with the original value', function() {
+ promise.then(success('A', 'aVal'), error());
+ promise.then(success('B', 'bErr', true), error());
+ promise.then(success('C', q.reject('cReason')), error());
+ promise.then(success('D', 'dVal'), error());
+
+ expect(logStr()).toBe('');
+ syncResolve(deferred, 'yup');
+ expect(logStr()).toBe('successA(yup); successB(yup); successC(yup); successD(yup)');
+ });
+
+
+ it('should reject all callbacks with the original reason', function() {
+ promise.then(success(), error('A', 'aVal'));
+ promise.then(success(), error('B', 'bEr', true));
+ promise.then(success(), error('C', q.reject('cReason')));
+ promise.then(success(), error('D', 'dVal'));
+
+ expect(logStr()).toBe('');
+ syncReject(deferred, 'noo!');
+ expect(logStr()).toBe('errorA(noo!); errorB(noo!); errorC(noo!); errorD(noo!)');
+ });
+
+
+ it('should propagate resolution and rejection between dependent promises', function() {
+ promise.then(success(1, 'x'), error('1')).
+ then(success(2, 'y', true), error('2')).
+ then(success(3), error(3, 'z', true)).
+ then(success(4), error(4, 'done')).
+ then(success(5), error(5));
+
+ expect(logStr()).toBe('');
+ syncResolve(deferred, 'sweet!');
+ expect(log).toEqual(['success1(sweet!)',
+ 'success2(x)',
+ 'error3(y)',
+ 'error4(z)',
+ 'success5(done)']);
+ });
+
+
+ it('should reject a derived promise if an exception is thrown while resolving its parent',
+ function() {
+ promise.then(success(1, 'oops', true)).
+ then(success(2), error(2));
+ syncResolve(deferred, 'done!');
+ expect(logStr()).toBe('success1(done!); error2(oops)');
+ });
+
+
+ it('should reject a derived promise if an exception is thrown while rejecting its parent',
+ function() {
+ promise.then(null, error(1, 'oops', true)).
+ then(success(2), error(2));
+ syncReject(deferred, 'timeout');
+ expect(logStr()).toBe('error1(timeout); error2(oops)');
+ });
+
+
+ it('should call success callback in the next turn even if promise is already resolved',
+ function() {
+ deferred.resolve('done!');
+
+ promise.then(success());
+ expect(logStr()).toBe('');
+
+ mockNextTick.flush();
+ expect(log).toEqual(['success(done!)']);
+ });
+
+
+ it('should call errpr callback in the next turn even if promise is already rejected',
+ function() {
+ deferred.reject('oops!');
+
+ promise.then(null, error());
+ expect(logStr()).toBe('');
+
+ mockNextTick.flush();
+ expect(log).toEqual(['error(oops!)']);
+ });
+ });
+ });
+ });
+
+
+ describe('reject', function() {
+ it('should package a string into a rejected promise', function() {
+ var rejectedPromise = q.reject('not gonna happen');
+ promise.then(success(), error());
+ syncResolve(deferred, rejectedPromise);
+ expect(log).toEqual(['error(not gonna happen)']);
+ });
+
+
+ it('should package an exception into a rejected promise', function() {
+ var rejectedPromise = q.reject(Error('not gonna happen'));
+ promise.then(success(), error());
+ syncResolve(deferred, rejectedPromise);
+ expect(log).toEqual(['error(Error: not gonna happen)']);
+ });
+ });
+
+
+ describe('when', function() {
+ describe('resolution', function() {
+ it('should call the success callback in the next turn when the value is a non-promise',
+ function() {
+ q.when('hello', success(), error());
+ expect(logStr()).toBe('');
+ mockNextTick.flush();
+ expect(logStr()).toBe('success(hello)');
+ });
+
+
+ it('should call the success callback in the next turn when the value is a resolved promise',
+ function() {
+ deferred.resolve('hello');
+ q.when(deferred.promise, success(), error());
+ expect(logStr()).toBe('');
+ mockNextTick.flush();
+ expect(logStr()).toBe('success(hello)');
+ });
+
+
+ it('should call the errback in the next turn when the value is a rejected promise', function() {
+ deferred.reject('nope');
+ q.when(deferred.promise, success(), error());
+ expect(logStr()).toBe('');
+ mockNextTick.flush();
+ expect(logStr()).toBe('error(nope)');
+ });
+
+
+ it('should call the success callback after the original promise is resolved',
+ function() {
+ q.when(deferred.promise, success(), error());
+ expect(logStr()).toBe('');
+ mockNextTick.flush();
+ expect(logStr()).toBe('');
+ syncResolve(deferred, 'hello');
+ expect(logStr()).toBe('success(hello)');
+ });
+
+
+ it('should call the errback after the orignal promise is rejected',
+ function() {
+ q.when(deferred.promise, success(), error());
+ expect(logStr()).toBe('');
+ mockNextTick.flush();
+ expect(logStr()).toBe('');
+ syncReject(deferred, 'nope');
+ expect(logStr()).toBe('error(nope)');
+ });
+ });
+
+
+ describe('optional callbacks', function() {
+ it('should not require success callback and propagate resolution', function() {
+ q.when('hi', null, error()).then(success(2), error());
+ expect(logStr()).toBe('');
+ mockNextTick.flush();
+ expect(logStr()).toBe('success2(hi)');
+ });
+
+
+ it('should not require success callback and propagate rejection', function() {
+ q.when(q.reject('sorry'), null, error(1)).then(success(), error(2));
+ expect(logStr()).toBe('');
+ mockNextTick.flush();
+ expect(logStr()).toBe('error1(sorry); error2(sorry)');
+ });
+
+
+ it('should not require errback and propagate resolution', function() {
+ q.when('hi', success(1, 'hello')).then(success(2), error());
+ expect(logStr()).toBe('');
+ mockNextTick.flush();
+ expect(logStr()).toBe('success1(hi); success2(hello)');
+ });
+
+
+ it('should not require errback and propagate rejection', function() {
+ q.when(q.reject('sorry'), success()).then(success(2), error(2));
+ expect(logStr()).toBe('');
+ mockNextTick.flush();
+ expect(logStr()).toBe('error2(sorry)');
+ });
+ });
+
+
+ describe('returned promise', function() {
+ it('should return a promise that can be resolved with a value returned from the success ' +
+ 'callback', function() {
+ q.when('hello', success(1, 'hi'), error()).then(success(2), error());
+ mockNextTick.flush();
+ expect(logStr()).toBe('success1(hello); success2(hi)');
+ });
+
+
+ it('should return a promise that can be rejected with a rejected promise returned from the ' +
+ 'success callback', function() {
+ q.when('hello', success(1, q.reject('sorry')), error()).then(success(), error(2));
+ mockNextTick.flush();
+ expect(logStr()).toBe('success1(hello); error2(sorry)');
+ });
+
+
+ it('should return a promise that can be resolved with a value returned from the errback',
+ function() {
+ q.when(q.reject('sorry'), success(), error(1, 'hi')).then(success(2), error());
+ mockNextTick.flush();
+ expect(logStr()).toBe('error1(sorry); success2(hi)');
+ });
+
+
+ it('should return a promise that can be rejected with a rejected promise returned from the ' +
+ 'errback', function() {
+ q.when(q.reject('sorry'), success(), error(1, q.reject('sigh'))).then(success(), error(2));
+ mockNextTick.flush();
+ expect(logStr()).toBe('error1(sorry); error2(sigh)');
+ });
+
+
+ it('should return a promise that can be resolved with a promise returned from the success ' +
+ 'callback', function() {
+ var deferred2 = defer();
+ q.when('hi', success(1, deferred2.promise), error()).then(success(2), error());
+ mockNextTick.flush();
+ expect(logStr()).toBe('success1(hi)');
+ syncResolve(deferred2, 'finally!');
+ expect(logStr()).toBe('success1(hi); success2(finally!)');
+ });
+
+
+ it('should return a promise that can be resolved with promise returned from the errback ' +
+ 'callback', function() {
+ var deferred2 = defer();
+ q.when(q.reject('sorry'), success(), error(1, deferred2.promise)).then(success(2), error());
+ mockNextTick.flush();
+ expect(logStr()).toBe('error1(sorry)');
+ syncResolve(deferred2, 'finally!');
+ expect(logStr()).toBe('error1(sorry); success2(finally!)');
+ });
+ });
+
+
+ describe('security', function() {
+ it('should call success callback only once even if the original promise gets fullfilled ' +
+ 'multiple times', function() {
+ var evilPromise = {
+ then: function(success, error) {
+ evilPromise.success = success;
+ evilPromise.error = error;
+ }
+ }
+
+ q.when(evilPromise, success(), error());
+ mockNextTick.flush();
+ expect(logStr()).toBe('');
+ evilPromise.success('done');
+ mockNextTick.flush(); // TODO(i) wrong queue, evil promise would be resolved outside of the
+ // scope.$apply lifecycle and in that case we should have some kind
+ // of fallback queue for calling our callbacks from. Otherwise the
+ // application will get stuck until something triggers next $apply.
+ expect(logStr()).toBe('success(done)');
+
+ evilPromise.success('evil is me');
+ evilPromise.error('burn burn');
+ expect(logStr()).toBe('success(done)');
+ });
+
+
+ it('should call errback only once even if the original promise gets fullfilled multiple ' +
+ 'times', function() {
+ var evilPromise = {
+ then: function(success, error) {
+ evilPromise.success = success;
+ evilPromise.error = error;
+ }
+ }
+
+ q.when(evilPromise, success(), error());
+ mockNextTick.flush();
+ expect(logStr()).toBe('');
+ evilPromise.error('failed');
+ expect(logStr()).toBe('error(failed)');
+
+ evilPromise.error('muhaha');
+ evilPromise.success('take this');
+ expect(logStr()).toBe('error(failed)');
+ });
+ });
+ });
+
+
+ describe('all', function() {
+ it('should take an array of promises and return a promise for an array of results', function() {
+ var deferred1 = defer(),
+ deferred2 = defer();
+
+ q.all([promise, deferred1.promise, deferred2.promise]).then(success(), error());
+ expect(logStr()).toBe('');
+ syncResolve(deferred, 'hi');
+ expect(logStr()).toBe('');
+ syncResolve(deferred2, 'cau');
+ expect(logStr()).toBe('');
+ syncResolve(deferred1, 'hola');
+ expect(logStr()).toBe('success([hi,hola,cau])');
+ });
+
+
+ it('should reject the derived promise if at least one of the promises in the array is rejected',
+ function() {
+ var deferred1 = defer(),
+ deferred2 = defer();
+
+ q.all([promise, deferred1.promise, deferred2.promise]).then(success(), error());
+ expect(logStr()).toBe('');
+ syncResolve(deferred2, 'cau');
+ expect(logStr()).toBe('');
+ syncReject(deferred1, 'oops');
+ expect(logStr()).toBe('error(oops)');
+ });
+
+
+ it('should ignore multiple resolutions of an (evil) array promise', function() {
+ var evilPromise = {
+ then: function(success, error) {
+ evilPromise.success = success;
+ evilPromise.error = error;
+ }
+ }
+
+ q.all([promise, evilPromise]).then(success(), error());
+ expect(logStr()).toBe('');
+
+ evilPromise.success('first');
+ evilPromise.success('muhaha');
+ evilPromise.error('arghhh');
+ expect(logStr()).toBe('');
+
+ syncResolve(deferred, 'done');
+ expect(logStr()).toBe('success([done,first])');
+ });
+ });
+
+
+ describe('exception logging', function() {
+ var mockExceptionLogger = {
+ log: [],
+ logger: function(e) {
+ mockExceptionLogger.log.push(e);
+ }
+ }
+
+
+ beforeEach(function() {
+ q = qFactory(mockNextTick.nextTick, mockExceptionLogger.logger),
+ defer = q.defer;
+ deferred = defer()
+ promise = deferred.promise;
+ log = [];
+ mockExceptionLogger.log = [];
+ });
+
+
+ describe('in then', function() {
+ it('should log exceptions thrown in a success callback and reject the derived promise',
+ function() {
+ var success1 = success(1, 'oops', true);
+ promise.then(success1).then(success(2), error(2));
+ syncResolve(deferred, 'done');
+ expect(logStr()).toBe('success1(done); error2(oops)');
+ expect(mockExceptionLogger.log).toEqual(['oops']);
+ });
+
+
+ it('should NOT log exceptions when a success callback returns rejected promise', function() {
+ promise.then(success(1, q.reject('rejected'))).then(success(2), error(2));
+ syncResolve(deferred, 'done');
+ expect(logStr()).toBe('success1(done); error2(rejected)');
+ expect(mockExceptionLogger.log).toEqual([]);
+ });
+
+
+ it('should log exceptions thrown in a errback and reject the derived promise', function() {
+ var error1 = error(1, 'oops', true);
+ promise.then(null, error1).then(success(2), error(2));
+ syncReject(deferred, 'nope');
+ expect(logStr()).toBe('error1(nope); error2(oops)');
+ expect(mockExceptionLogger.log).toEqual(['oops']);
+ });
+
+
+ it('should NOT log exceptions when an errback returns a rejected promise', function() {
+ promise.then(null, error(1, q.reject('rejected'))).then(success(2), error(2));
+ syncReject(deferred, 'nope');
+ expect(logStr()).toBe('error1(nope); error2(rejected)');
+ expect(mockExceptionLogger.log).toEqual([]);
+ });
+ });
+
+
+ describe('in when', function() {
+ it('should log exceptions thrown in a success callback and reject the derived promise',
+ function() {
+ var success1 = success(1, 'oops', true);
+ q.when('hi', success1, error()).then(success(), error(2));
+ mockNextTick.flush();
+ expect(logStr()).toBe('success1(hi); error2(oops)');
+ expect(mockExceptionLogger.log).toEqual(['oops']);
+ });
+
+
+ it('should NOT log exceptions when a success callback returns rejected promise', function() {
+ q.when('hi', success(1, q.reject('rejected'))).then(success(2), error(2));
+ mockNextTick.flush();
+ expect(logStr()).toBe('success1(hi); error2(rejected)');
+ expect(mockExceptionLogger.log).toEqual([]);
+ });
+
+
+ it('should log exceptions thrown in a errback and reject the derived promise', function() {
+ var error1 = error(1, 'oops', true);
+ q.when(q.reject('sorry'), success(), error1).then(success(), error(2));
+ mockNextTick.flush();
+ expect(logStr()).toBe('error1(sorry); error2(oops)');
+ expect(mockExceptionLogger.log).toEqual(['oops']);
+ });
+
+
+ it('should NOT log exceptions when an errback returns a rejected promise', function() {
+ q.when(q.reject('sorry'), success(), error(1, q.reject('rejected'))).
+ then(success(2), error(2));
+ mockNextTick.flush();
+ expect(logStr()).toBe('error1(sorry); error2(rejected)');
+ expect(mockExceptionLogger.log).toEqual([]);
+ });
+ });
+ });
+});
--
cgit v1.2.3