25

In the following code snippet error 1 and success 2 will be logged. How can I can I propagate error callbacks being invoked rather than the success callbacks being invoked if the original deferred is rejected.

angular.module("Foo", []);
angular
.module("Foo")
.controller("Bar", function ($q) {
    var deferred = $q.defer();
      deferred.reject();

      deferred.promise
          .then(
              /*success*/function () { console.log("success 1"); },
              /*error*/function () { console.log("error 1"); })
          .then(
              /*success*/function () { console.log("success 2"); },
              /*error*/function () { console.log("error 2"); });
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="Foo">
    <div ng-controller="Bar"></div>
</div>
Steven Wexler
  • 14,113
  • 7
  • 42
  • 75
  • 2
    use `.catch()` instead of `.then(onSuccess, onFailure)` – aarosil Oct 18 '14 at 16:53
  • How would that help? That's just a shorthand method for then. If you look at Angular's source code you'll find `"catch": function(callback) { return this.then(null, callback); }`. – Steven Wexler Oct 18 '14 at 16:57
  • 1
    Well, if you only have success handler in `then`, it would propagate down to the `catch`. You implicitly reject without using `$q.reject` simply by not handling it – aarosil Oct 18 '14 at 17:06
  • Ok, what you're telling me is very useful! `then(angular.noop, angular.noop)` is different than calling `then(angular.noop, null)` because angular will assume you did not try to correct the error if you don't pass in an error callback, so it will propagate the rejection by presumably calling `$q.reject` (or something analgous) under the hood. However, it will assume you tried to correct the error if you provide an error callback. To not confuse other readers, we should point out this has nothing to do with `.catch`. – Steven Wexler Oct 18 '14 at 17:21
  • They are no different - both are handlers. `catch` is only shortcut for `.then(null, fn)`. I find more readable too -- to leave `then` only for successes and place `catch` as needed to get as finegrained control as you want, doing things like instanceOf on the errors and handling or rethrowing etc as you need to (`reject` inside `then` appears odd to me). – aarosil Oct 18 '14 at 17:59
  • check ths out: http://stackoverflow.com/a/24663315/2845029 – aarosil Oct 18 '14 at 18:08
  • They are different. Try it for yourself. You'll find later success callbacks are invoked with the first example and later error callbacks are invoked with the second example. The simpliest code to try is: `.then(angular.noop, angular.noop).catch(function () { console.log("caught"); }` vs `.then(angular.noop, null).catch(function () { console.log("caught"); }`. You'll find the log statement is never printed in the first code snippet. Note, the second code snippet is equivalent to `.then(angular.noop).catch(function () { console.log("caught"); })`. – Steven Wexler Oct 18 '14 at 18:21
  • On a related note, `.then(function () { /*do stuff*/}).catch(function () { console.log("caught"); })` is equivalent to `.then(function () { /*do stuff*/}).then(null, function () { /*handle errors*/ })` and arguably more useful than `.then(function {/*do stuff*/}, function () { /*handle errors*/})` because you can handle the errors from your success callback. – Steven Wexler Oct 18 '14 at 18:25

3 Answers3

31

Error is propagate by returning $q.reject in the error callback

    var deferred = $q.defer();
      deferred.reject();

      deferred.promise
          .then(
              /*success*/function () { console.log("success 1"); },
              /*error*/function () { console.log("error 1"); return $q.reject('error 1')})
          .then(
              /*success*/function () { console.log("success 2"); },
              /*error*/function () { console.log("error 2"); });
});
Chandermani
  • 42,177
  • 11
  • 82
  • 86
15

think of success/failure as try/catch

try{
    var val = dummyPromise();
} catch (e){
    val = "SomeValue";
}

if catch does not throws an exception, it is considered that the error is handled and hence outer calling function does not sees the error which occured in inner function.

Similar stuff happening here, you have to return return $q.reject(); from a promise in order for the next promise in the chain to fail too. See example plunker: http://plnkr.co/edit/porOG8qVg2GkeddzVHu3?p=preview

The reason is: Your error handler may take action to correct the error. In your error-function your dealing with the error,if not specified otherwise, it will return a new promise which is resolved. Therefore it is not reasonable to have the next promise failing by default (try-catch analogy).

By the way, you can return $q.reject() even from a success handler, if you sense an error condition, to have the next promise in the chain failing. You're catching the error and handling it - so it gets to the success handler. If you want to reject it, you have to do it by returning $q.reject();

harishr
  • 16,726
  • 7
  • 70
  • 114
  • Thanks for explaining WHY the error handler needs to return $q.reject() in order to continue propagating the error. – wholladay Dec 19 '18 at 23:20
6

To sum the comments up, to propagate errors in the promise chain, either:

1) Do not provide an errorCallback for then:

deferred.promise
.then(
  /*success*/function () { console.log("success 1"); },
.then(
  /*success*/function () { console.log("success 2"); },
  /*error*/function () { console.log("error 2"); }); // gets called

Or

2) Return $q.reject() from the errorCallback:

deferred.promise
.then(
  /*success*/function () { console.log("success 1"); },
  /*error*/function (err) { console.log("error 1"); return $q.reject(err); });
.then(
  /*success*/function () { console.log("success 2"); },
  /*error*/function () { console.log("error 2"); }); // gets called

From the angular $q.reject documentation:

This api should be used to forward rejection in a chain of promises.
user1338062
  • 9,351
  • 3
  • 54
  • 56
  • 2
    That part of the documentation is VERY easy to miss, since the "Chaining Promises" section that comes before it, makes absolutely no mention of the fact that promise errors do not automatically propagate. – AgmLauncher Jul 27 '16 at 14:00