$q promise error callback chains

12,018

Solution 1

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"); });
});

Solution 2

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();

Solution 3

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.
Share:
12,018

Related videos on Youtube

Steven Wexler
Author by

Steven Wexler

Freelance Software Developer Formerly a Software Developer at Hurdlr Formerly a Lead engineer at Applied Predictive Technologies Author of exceptions.js Contributor to Code Ducky

Updated on November 22, 2020

Comments

  • Steven Wexler
    Steven Wexler over 3 years

    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>

    • aarosil
      aarosil over 9 years
      use .catch() instead of .then(onSuccess, onFailure)
    • Steven Wexler
      Steven Wexler over 9 years
      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); }.
    • aarosil
      aarosil over 9 years
      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
    • Steven Wexler
      Steven Wexler over 9 years
      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.
    • aarosil
      aarosil over 9 years
      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
      aarosil over 9 years
    • Steven Wexler
      Steven Wexler over 9 years
      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
      Steven Wexler over 9 years
      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.
  • AgmLauncher
    AgmLauncher almost 8 years
    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.
  • wholladay
    wholladay over 5 years
    Thanks for explaining WHY the error handler needs to return $q.reject() in order to continue propagating the error.