From 42aad0e91b48df911a1f03f34b1610df58109321 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Wed, 17 Apr 2013 14:15:27 +0800 Subject: [PATCH] feat($q): always() method Added new always(callback) method. Also added tests and updated documentation. --- src/ng/q.js | 41 +++++++++++++++ test/ng/qSpec.js | 132 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) diff --git a/src/ng/q.js b/src/ng/q.js index 33ca663af555..22c9caa9a973 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 822cde6b2f66..0c59db89496b 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)'); + }); + + }); + + }); + }); }); });