$q promise error callback chains
Asked Answered
U

3

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>
Unaccountable answered 18/10, 2014 at 16:49 Comment(8)
use .catch() instead of .then(onSuccess, onFailure)Senegal
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); }.Unaccountable
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 itSenegal
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.Unaccountable
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).Senegal
check ths out: https://mcmap.net/q/76403/-when-is-then-success-fail-considered-an-antipattern-for-promisesSenegal
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"); }).Unaccountable
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.Unaccountable
G
32

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"); });
});
Gibbon answered 18/10, 2014 at 16:59 Comment(0)
S
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();

Swaraj answered 18/10, 2014 at 17:5 Comment(1)
Thanks for explaining WHY the error handler needs to return $q.reject() in order to continue propagating the error.Vorlage
S
7

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.
Skantze answered 11/9, 2015 at 19:26 Comment(1)
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.Ellipsoid

© 2022 - 2024 — McMap. All rights reserved.