Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat($q): add $q.always() method
Browse files Browse the repository at this point in the history
Add $q.always(callback) method that is always called whether the promise is successful or fails; includes unit tests and updates
documentation.
  • Loading branch information
Laurent Cozic authored and petebacondarwin committed Apr 24, 2013
1 parent b1157aa commit 6605adf
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 0 deletions.
41 changes: 41 additions & 0 deletions src/ng/q.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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);
});
}
}
};
Expand Down
132 changes: 132 additions & 0 deletions test/ng/qSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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)');
});

});

});
});
});
});

Expand Down

1 comment on commit 6605adf

@woeye
Copy link

@woeye woeye commented on 6605adf Apr 25, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! The lack of it was sometimes really annoying :)

Please sign in to comment.