diff --git a/src/ng/q.js b/src/ng/q.js index 7d17512448b8..4aa29fa1c677 100644 --- a/src/ng/q.js +++ b/src/ng/q.js @@ -282,6 +282,16 @@ function qFactory(nextTick, exceptionHandler) { return this.then(null, callback); }, + always: function(callback) { + return this.then(function(value) { + safeInvoke(callback, value); + return value; + }, function(reason) { + safeInvoke(callback, reason); + return reject(reason); + }); + }, + "finally": function(callback, progressBack) { return this.then(function(value) { return handleCallback(value, true, callback); @@ -472,6 +482,14 @@ function qFactory(nextTick, exceptionHandler) { } }; + var safeInvoke = function safeInvoke(callback, value) { + try { + callback(value); + } catch(e) { + + } + }; + /** * @ngdoc method * @name $q#when diff --git a/test/ng/qSpec.js b/test/ng/qSpec.js index b4b17295dc02..0f17a3a382d8 100644 --- a/test/ng/qSpec.js +++ b/test/ng/qSpec.js @@ -218,6 +218,7 @@ describe('q', function() { var promise = q(noop); expect(typeof promise.then).toBe('function'); expect(typeof promise.catch).toBe('function'); + expect(typeof promise.always).toBe('function'); expect(typeof promise.finally).toBe('function'); }); @@ -790,6 +791,40 @@ describe('q', function() { expect(logStr()).toBe('error1(foo)->reject(foo); error2(foo)->reject(foo)'); }); }); + + describe('always', function() { + it('should be not effect reject reason', function() { + var promise = createPromise(); + promise.always(error(1, 'bar')).then(null, error(2)); + reject('foo'); + mockNextTick.flush(); + expect(logStr()).toBe('error1(foo)->bar; error2(foo)->reject(foo)'); + }); + + it('should be not effect reject reason if exception thrown', function() { + var promise = createPromise(); + promise.always(error(1, 'bar', true)).then(null, error(2)); + reject('foo'); + mockNextTick.flush(); + expect(logStr()).toBe('error1(foo)->throw(bar); error2(foo)->reject(foo)'); + }); + + it('should be not effect fulfillment value', function() { + var promise = createPromise(); + promise.always(success(1, 'bar')).then(success(2)); + resolve('foo'); + mockNextTick.flush(); + expect(logStr()).toBe('success1(foo)->bar; success2(foo)->foo'); + }); + + it('should be not effect fulfillment value if exception thrown', function() { + var promise = createPromise(); + promise.always(success(1, 'bar', true)).then(success(2)); + resolve('foo'); + mockNextTick.flush(); + expect(logStr()).toBe('success1(foo)->throw(bar); success2(foo)->foo'); + }); + }); }); }); @@ -1132,6 +1167,10 @@ describe('q', function() { expect(typeof promise['catch']).toBe('function'); }); + it('should have an always method', function() { + expect(typeof promise.always).toBe('function'); + }); + it('should have a finally method', function() { expect(typeof promise['finally']).toBe('function'); }); @@ -1535,6 +1574,32 @@ describe('q', function() { expect(logStr()).toBe('error1(foo)->reject(foo); error2(foo)->reject(foo)'); }); }); + + describe('always', function() { + it('should be not effect reject reason', function() { + promise.always(error(1, 'bar')).then(null, error(2)); + syncReject(deferred, 'foo'); + expect(logStr()).toBe('error1(foo)->bar; error2(foo)->reject(foo)'); + }); + + it('should be not effect reject reason if exception thrown', function() { + promise.always(error(1, 'bar', true)).then(null, error(2)); + syncReject(deferred, 'foo'); + expect(logStr()).toBe('error1(foo)->throw(bar); error2(foo)->reject(foo)'); + }); + + it('should be not effect fulfillment value', function() { + promise.always(success(1, 'bar')).then(success(2)); + syncResolve(deferred, 'foo'); + expect(logStr()).toBe('success1(foo)->bar; success2(foo)->foo'); + }); + + it('should be not effect fulfillment value if exception thrown', function() { + promise.always(success(1, 'bar', true)).then(success(2)); + syncResolve(deferred, 'foo'); + expect(logStr()).toBe('success1(foo)->throw(bar); success2(foo)->foo'); + }); + }); }); });