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

Commit 6605adf

Browse files
Laurent Cozicpetebacondarwin
Laurent Cozic
authored andcommitted
feat($q): add $q.always() method
Add $q.always(callback) method that is always called whether the promise is successful or fails; includes unit tests and updates documentation.
1 parent b1157aa commit 6605adf

File tree

2 files changed

+173
-0
lines changed

2 files changed

+173
-0
lines changed

src/ng/q.js

+41
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@
9191
* This method *returns a new promise* which is resolved or rejected via the return value of the
9292
* `successCallback` or `errorCallback`.
9393
*
94+
* - `always(callback)` – allows you to observe either the fulfillment or rejection of a promise,
95+
* but to do so without modifying the final value. This is useful to release resources or do some
96+
* clean-up that needs to be done whether the promise was rejected or resolved. See the [full
97+
* specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for
98+
* more information.
9499
*
95100
* # Chaining promises
96101
*
@@ -236,6 +241,42 @@ function qFactory(nextTick, exceptionHandler) {
236241
}
237242

238243
return result.promise;
244+
},
245+
always: function(callback) {
246+
247+
function makePromise(value, resolved) {
248+
var result = defer();
249+
if (resolved) {
250+
result.resolve(value);
251+
} else {
252+
result.reject(value);
253+
}
254+
return result.promise;
255+
}
256+
257+
function handleCallback(value, isResolved) {
258+
var callbackOutput = null;
259+
try {
260+
callbackOutput = (callback ||defaultCallback)();
261+
} catch(e) {
262+
return makePromise(e, false);
263+
}
264+
if (callbackOutput && callbackOutput.then) {
265+
return callbackOutput.then(function() {
266+
return makePromise(value, isResolved);
267+
}, function(error) {
268+
return makePromise(error, false);
269+
});
270+
} else {
271+
return makePromise(value, isResolved);
272+
}
273+
}
274+
275+
return this.then(function(value) {
276+
return handleCallback(value, true);
277+
}, function(error) {
278+
return handleCallback(error, false);
279+
});
239280
}
240281
}
241282
};

test/ng/qSpec.js

+132
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,10 @@ describe('q', function() {
345345
it('should have a then method', function() {
346346
expect(typeof promise.then).toBe('function');
347347
});
348+
349+
it('should have a always method', function() {
350+
expect(typeof promise.always).toBe('function');
351+
});
348352

349353

350354
describe('then', function() {
@@ -461,6 +465,134 @@ describe('q', function() {
461465
expect(log).toEqual(['error(oops!)']);
462466
});
463467
});
468+
469+
470+
describe('always', function() {
471+
472+
it('should not take an argument',
473+
function() {
474+
promise.always(success(1))
475+
syncResolve(deferred, 'foo');
476+
expect(logStr()).toBe('success1()');
477+
});
478+
479+
describe("when the promise is fulfilled", function () {
480+
481+
it('should call the callback',
482+
function() {
483+
promise.then(success(1))
484+
.always(success(2))
485+
syncResolve(deferred, 'foo');
486+
expect(logStr()).toBe('success1(foo); success2()');
487+
});
488+
489+
it('should fulfill with the original value',
490+
function() {
491+
promise.always(success(1))
492+
.then(success(2), error(2))
493+
syncResolve(deferred, 'foo');
494+
expect(logStr()).toBe('success1(); success2(foo)');
495+
});
496+
497+
describe("when the callback returns a promise", function() {
498+
499+
describe("that is fulfilled", function() {
500+
it("should fulfill with the original reason after that promise resolves",
501+
function () {
502+
var returnedDef = defer()
503+
returnedDef.resolve('bar');
504+
promise.always(success(1, returnedDef.promise))
505+
.then(success(2))
506+
syncResolve(deferred, 'foo');
507+
expect(logStr()).toBe('success1(); success2(foo)');
508+
});
509+
});
510+
511+
describe("that is rejected", function() {
512+
it("should reject with this new rejection reason",
513+
function () {
514+
var returnedDef = defer()
515+
returnedDef.reject('bar');
516+
promise.always(success(1, returnedDef.promise))
517+
.then(success(2), error(1))
518+
syncResolve(deferred, 'foo');
519+
expect(logStr()).toBe('success1(); error1(bar)');
520+
});
521+
});
522+
523+
});
524+
525+
describe("when the callback throws an exception", function() {
526+
it("should reject with this new exception", function() {
527+
promise.always(error(1, "exception", true))
528+
.then(success(1), error(2))
529+
syncResolve(deferred, 'foo');
530+
expect(logStr()).toBe('error1(); error2(exception)');
531+
});
532+
});
533+
534+
});
535+
536+
537+
describe("when the promise is rejected", function () {
538+
539+
it("should call the callback", function () {
540+
promise.always(success(1))
541+
.then(success(2), error(1))
542+
syncReject(deferred, 'foo');
543+
expect(logStr()).toBe('success1(); error1(foo)');
544+
});
545+
546+
it('should reject with the original reason', function() {
547+
promise.always(success(1), "hello")
548+
.then(success(2), error(2))
549+
syncReject(deferred, 'original');
550+
expect(logStr()).toBe('success1(); error2(original)');
551+
});
552+
553+
describe("when the callback returns a promise", function() {
554+
555+
describe("that is fulfilled", function() {
556+
557+
it("should reject with the original reason after that promise resolves", function () {
558+
var returnedDef = defer()
559+
returnedDef.resolve('bar');
560+
promise.always(success(1, returnedDef.promise))
561+
.then(success(2), error(2))
562+
syncReject(deferred, 'original');
563+
expect(logStr()).toBe('success1(); error2(original)');
564+
});
565+
566+
});
567+
568+
describe("that is rejected", function () {
569+
570+
it("should reject with the new reason", function() {
571+
var returnedDef = defer()
572+
returnedDef.reject('bar');
573+
promise.always(success(1, returnedDef.promise))
574+
.then(success(2), error(1))
575+
syncResolve(deferred, 'foo');
576+
expect(logStr()).toBe('success1(); error1(bar)');
577+
});
578+
579+
});
580+
581+
});
582+
583+
describe("when the callback throws an exception", function() {
584+
585+
it("should reject with this new exception", function() {
586+
promise.always(error(1, "exception", true))
587+
.then(success(1), error(2))
588+
syncResolve(deferred, 'foo');
589+
expect(logStr()).toBe('error1(); error2(exception)');
590+
});
591+
592+
});
593+
594+
});
595+
});
464596
});
465597
});
466598

0 commit comments

Comments
 (0)