aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/ng/q.js41
-rw-r--r--test/ng/qSpec.js132
2 files changed, 173 insertions, 0 deletions
diff --git a/src/ng/q.js b/src/ng/q.js
index 33ca663a..22c9caa9 100644
--- a/src/ng/q.js
+++ b/src/ng/q.js
@@ -91,6 +91,11 @@
* This method *returns a new promise* which is resolved or rejected via the return value of the
* `successCallback` or `errorCallback`.
*
+ * - `always(callback)` – allows you to observe either the fulfillment or rejection of a promise,
+ * but to do so without modifying the final value. This is useful to release resources or do some
+ * clean-up that needs to be done whether the promise was rejected or resolved. See the [full
+ * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
+ * more information.
*
* # Chaining promises
*
@@ -236,6 +241,42 @@ function qFactory(nextTick, exceptionHandler) {
}
return result.promise;
+ },
+ always: function(callback) {
+
+ function makePromise(value, resolved) {
+ var result = defer();
+ if (resolved) {
+ result.resolve(value);
+ } else {
+ result.reject(value);
+ }
+ return result.promise;
+ }
+
+ function handleCallback(value, isResolved) {
+ var callbackOutput = null;
+ try {
+ callbackOutput = (callback ||defaultCallback)();
+ } catch(e) {
+ return makePromise(e, false);
+ }
+ if (callbackOutput && callbackOutput.then) {
+ return callbackOutput.then(function() {
+ return makePromise(value, isResolved);
+ }, function(error) {
+ return makePromise(error, false);
+ });
+ } else {
+ return makePromise(value, isResolved);
+ }
+ }
+
+ return this.then(function(value) {
+ return handleCallback(value, true);
+ }, function(error) {
+ return handleCallback(error, false);
+ });
}
}
};
diff --git a/test/ng/qSpec.js b/test/ng/qSpec.js
index 822cde6b..0c59db89 100644
--- a/test/ng/qSpec.js
+++ b/test/ng/qSpec.js
@@ -345,6 +345,10 @@ describe('q', function() {
it('should have a then method', function() {
expect(typeof promise.then).toBe('function');
});
+
+ it('should have a always method', function() {
+ expect(typeof promise.always).toBe('function');
+ });
describe('then', function() {
@@ -461,6 +465,134 @@ describe('q', function() {
expect(log).toEqual(['error(oops!)']);
});
});
+
+
+ describe('always', function() {
+
+ it('should not take an argument',
+ function() {
+ promise.always(success(1))
+ syncResolve(deferred, 'foo');
+ expect(logStr()).toBe('success1()');
+ });
+
+ describe("when the promise is fulfilled", function () {
+
+ it('should call the callback',
+ function() {
+ promise.then(success(1))
+ .always(success(2))
+ syncResolve(deferred, 'foo');
+ expect(logStr()).toBe('success1(foo); success2()');
+ });
+
+ it('should fulfill with the original value',
+ function() {
+ promise.always(success(1))
+ .then(success(2), error(2))
+ syncResolve(deferred, 'foo');
+ expect(logStr()).toBe('success1(); success2(foo)');
+ });
+
+ describe("when the callback returns a promise", function() {
+
+ describe("that is fulfilled", function() {
+ it("should fulfill with the original reason after that promise resolves",
+ function () {
+ var returnedDef = defer()
+ returnedDef.resolve('bar');
+ promise.always(success(1, returnedDef.promise))
+ .then(success(2))
+ syncResolve(deferred, 'foo');
+ expect(logStr()).toBe('success1(); success2(foo)');
+ });
+ });
+
+ describe("that is rejected", function() {
+ it("should reject with this new rejection reason",
+ function () {
+ var returnedDef = defer()
+ returnedDef.reject('bar');
+ promise.always(success(1, returnedDef.promise))
+ .then(success(2), error(1))
+ syncResolve(deferred, 'foo');
+ expect(logStr()).toBe('success1(); error1(bar)');
+ });
+ });
+
+ });
+
+ describe("when the callback throws an exception", function() {
+ it("should reject with this new exception", function() {
+ promise.always(error(1, "exception", true))
+ .then(success(1), error(2))
+ syncResolve(deferred, 'foo');
+ expect(logStr()).toBe('error1(); error2(exception)');
+ });
+ });
+
+ });
+
+
+ describe("when the promise is rejected", function () {
+
+ it("should call the callback", function () {
+ promise.always(success(1))
+ .then(success(2), error(1))
+ syncReject(deferred, 'foo');
+ expect(logStr()).toBe('success1(); error1(foo)');
+ });
+
+ it('should reject with the original reason', function() {
+ promise.always(success(1), "hello")
+ .then(success(2), error(2))
+ syncReject(deferred, 'original');
+ expect(logStr()).toBe('success1(); error2(original)');
+ });
+
+ describe("when the callback returns a promise", function() {
+
+ describe("that is fulfilled", function() {
+
+ it("should reject with the original reason after that promise resolves", function () {
+ var returnedDef = defer()
+ returnedDef.resolve('bar');
+ promise.always(success(1, returnedDef.promise))
+ .then(success(2), error(2))
+ syncReject(deferred, 'original');
+ expect(logStr()).toBe('success1(); error2(original)');
+ });
+
+ });
+
+ describe("that is rejected", function () {
+
+ it("should reject with the new reason", function() {
+ var returnedDef = defer()
+ returnedDef.reject('bar');
+ promise.always(success(1, returnedDef.promise))
+ .then(success(2), error(1))
+ syncResolve(deferred, 'foo');
+ expect(logStr()).toBe('success1(); error1(bar)');
+ });
+
+ });
+
+ });
+
+ describe("when the callback throws an exception", function() {
+
+ it("should reject with this new exception", function() {
+ promise.always(error(1, "exception", true))
+ .then(success(1), error(2))
+ syncResolve(deferred, 'foo');
+ expect(logStr()).toBe('error1(); error2(exception)');
+ });
+
+ });
+
+ });
+ });
});
});