diff options
| -rw-r--r-- | src/ng/q.js | 41 | ||||
| -rw-r--r-- | test/ng/qSpec.js | 132 | 
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)'); +            }); +             +          }); + +        });         +      });      });    }); | 
