8

I'm trying to figure out a good way to say "Do all of these things, but bail in the case that any of them fails"

What I have right now:

var defer = $q.defer();

this
    .load( thingy ) // returns a promise

    .then( this.doSomethingA.bind( this ) )
    .then( this.doSomethingB.bind( this ) )
    .then( this.doSomethingC.bind( this ) )
    .then( this.doSomethingD.bind( this ) )

    .then( function(){
        defer.resolve( this );
    } );
    ;

return defer.promise;

What I ultimately want is to somehow catch any error on that chain so I can pass it on to the defer promise above. I don't particularly care if the syntax is kept similar to what I have above.

Or even if anyone can just tell me how to stop the above chain.

  • 1
    @user2246674 -- rejecting it doesn't seem to stop the chain though, the next one just continues –  May 30 '13 at 23:22
  • 1
    Don't know what I was thinking .. – user2246674 May 30 '13 at 23:30
  • 2
    This is a horrid example of the [deferred antipattern](http://stackoverflow.com/q/23803743/1048572)! Screw that `defer`, just `return` the `this.load(…).then(A).then(B).then(C).then(D);` chain! – Bergi Jul 01 '15 at 17:58

6 Answers6

7

You can stop angularjs chain by returning rejected promise inside any then callback.

load()
.then(doA)
.then(doB)
.then(doC)
.then(doD);

where doA, doB, doC, doD can have logic like this:

var doA = function() {
    if(shouldFail) {
        return $q.reject();
    }
}
Max Podriezov
  • 958
  • 12
  • 14
  • 1
    +1 What did it for me is the `return` part of `return $q.reject()`. Lots of searching led to all kinds of different ways, but to actually return the $q.reject() instead of just calling it is what actually kills the chain. Especially if you're trying to do something that isn't an HTTP failure, but a 'failure' in the sense that the model returned with an expected negative response. – coblr Oct 27 '15 at 23:19
  • Another way to stop the chain, say you want to show errors that happened during chain execution, you can just `throw new Error()` – Max Podriezov Oct 30 '15 at 07:53
  • Returning rejected promise will not stop the chain. – user3631341 Mar 05 '18 at 10:53
3

I just stumbled across this and realized all these answers are terribly outdated. Here is the proper way to handle this for anyone that happens to find this post.

// Older code
return this.load(thing)
  .then(this.doA, $q.reject)
  .then(this.doB, $q.reject)
  .then(this.doC, $q.reject)
  .then(this.doD, $q.reject)
  .then(null, $q.reject);


// Updated code
// Returns the final promise with the success handlers and a unified error handler
return this.load(thing)
  .then(this.doA)
  .then(this.doB)
  .then(this.doC)
  .then(this.doD)
  .catch(this.handleErrors); // Alternatively, this can be left off if you just need to reject the promise since the promise is already rejected.
  // `.catch` is an alias for `.then(null, this.handleErrors);`
Michael Stramel
  • 1,337
  • 1
  • 16
  • 18
  • `.catch` didn't exist in angular's `$q` when this question was asked :) –  Jul 29 '15 at 07:19
  • @zyklus, you are correct but `.catch` is just an alias for `.then(null, fn)`. Which should have been valid then. – Michael Stramel Jul 29 '15 at 07:21
  • It didn't used to pass errors through, i.e. it would only catch errors from the previous `then`, which is why I wrote all the chain reject nonsense below. –  Jul 29 '15 at 07:25
  • If that was the case then using `$q.reject` as the second param to each `.then` would have been correct. I will leave my answer as an updated version that properly handles promises whereas the accepted answer still uses an anti-pattern. – Michael Stramel Jul 29 '15 at 07:29
  • Does not stop the chain. Catch returns a promise. – user3631341 Mar 05 '18 at 10:53
2

You should be able to do that same thing by:

var defer = $q.defer();

this
    .load( thingy ) // returns a promise

    .then( this.doSomethingA.bind( this ), $q.reject )
    .then( this.doSomethingB.bind( this ), $q.reject )
    .then( this.doSomethingC.bind( this ), $q.reject )
    .then( this.doSomethingD.bind( this ), $q.reject )

    .then( defer.resolve.bind( defer, this ), defer.reject.bind( defer ) );
    ;

return defer.promise;
marcoseu
  • 3,892
  • 2
  • 16
  • 35
1

The best way to handle this and also to catch the problem is the .catch block. Inside any .then block that you wanna kill the promise chain, yes use:

 return $q.reject();

However extend it like so...

 return $q.reject(new Error('Error Message Here'));

Now in the catch method you will have this

 .catch(function(err) {
     console.log(err); //This will log the above 'Error Message Here'
 });

Now we throw and handle the promise error correctly in the fashion promise failures were meant to be handled.

Sten Muchow
  • 6,623
  • 4
  • 36
  • 46
0

Okay, this works but I don't like it... Awaiting something better :)

It just seems dirty to create a promise for the sole sake of immediately rejecting it

myApp
    .factory( 'chainReject', [ '$q', function( $q ){
        return function( err ){
            var defer = $q.defer();
            defer.reject( err );

            return defer.promise;
        }
    } ] );

...

var defer = $q.defer();

this
    .load( thingy ) // returns a promise

    .then( this.doSomethingA.bind( this ), chainReject )
    .then( this.doSomethingB.bind( this ), chainReject )
    .then( this.doSomethingC.bind( this ), chainReject )
    .then( this.doSomethingD.bind( this ), chainReject )

    .then( defer.resolve.bind( defer, this ), defer.reject.bind( defer ) );
    ;

return defer.promise;
0

Looks like this use case has been anticipated and addressed with the use of $q.reject(reason)

anewcomer
  • 975
  • 11
  • 6