Break out of a Promise "then" chain with errorCallback
Asked Answered
L

4

9

-- EDIT --

I encountered a weird thing recently about promises, but I guess it's maybe because it's against the philosophy of promises.

Considering the following code :

// Assuming Auth is just a simple lib doing http requests with promises
Auth.signup()
 .then(succCall, errCall)
 .then(loginSucc, loginErr)

// My callbacks here
function succCall (){
 // OK, send second promise
 console.log('succCall');
 return Auth.login();
}

function errCall(){
 // I do some things here and now
 // I want to break out from here
 console.log('errCall');
}

function loginSucc(){
 // This is the callback of the login method when it went OK
 // I want to enter here ONLY if with go through the succCall
 console.log('loginSucc');
}

function loginErr(){
 // This is the callback of the login method when it went not ok
 // I want to enter here ONLY if with go through the succCall
 console.log('loginErr');
}

Here if something goes wrong in Auth.signup(), this is what show :

  • errCall, loginSucc

if i do a $q.reject() in the errCall this is what happens :

  • errCall, loginErr

and this is what i want :

  • errCall... finish, stop here

Now, the problem is, it goes in errCall when signup goes wrong, that's good, but then it enters loginSucc...

I want to break out of the then chain when any errorCallback (which is errCall or loginErr here) is encountered.

-- EDIT --

I think i was misunderstood by some mean, i want to totally break the chain without check in any other "then" if something went wrong.

As if i was saying : if first then is wrong stop here, if first then ok continue, if second "then" ok continue, if third "then" wrong, stop

// Just like if i did the following but by chainning "then" methods
// My callbacks here
function succCall (){
 // OK, send second promise
 return Auth.login().then(loginSucc, loginErr);
}

My point is, i don't want only one error handler if i have many "then" chained

Lotson answered 21/12, 2015 at 15:38 Comment(8)
I think to break the chain you have to reject the promise inside the errCall function.Robynroc
@Robynroc nope a reject just enters the next error, so loginErr here, and this is what i don't wantLotson
@SoluableNonagon has the right answer - use catch and skip your errCall and loginErr functions, and place their logic within the catch. You'll have to look at the error that the catch callback caught but it is the one way to accomplish what you want.Mcghee
the question was not to find an alternative, because what you are telling here is an alternative, this is not something that break's out from the chain. I know this method but it's not the one i want, but thanks anywayLotson
can you try to break it down in 2 parts? Auth.signup() .then(succCall, errCall) , succCall.then(loginSucc, loginErr), something like thatRobynroc
well ideally that's what i would like but how to break it down ? @RobynrocLotson
Your only option is to return a promise that is never resolved instead of rejecting in the first error handler. See my updated answer below.Ratafia
What you're trying to achieve is considered an anti-pattern. A chain should ideally be a single success scenario, with a catch to handle potential errors. If you want to do more branching in the chain, it easily becomes a maintenance nightmare. Ask yourself why you really want to handle intermediate errors. Do you want to log something? Just use a specific error message. Do you want to clean up? Use a specific error type. Do you want to do something advanced? Break the chain in parts.Prato
B
2

errCall function needs tor return a promise, and that promise needs to be rejected for loginErr to be fired.

function errCall(){
   // i do some things here and now

   return $q(function(resolve, reject) {
        // auto reject
        reject();
   });


}

Alternatively try .catch:

Auth.signup()
 .then(succCall)
 .then(loginSucc)
 .catch(function(err){
      // caught error, problem is you won't know which function errored out, so you'll need to look at the error response
 });
Brahui answered 21/12, 2015 at 15:47 Comment(1)
this is exactly what id don't want, i don't want anything to be donc if it enters errCall, all the rest should be ignored, just as in a basic callback syntax. The second then is for me the callback of the succCall and not the errCall that's the main point hereLotson
A
2

What is effectively happening is this:

    try {
        try {
            var a = succCall();
        } catch(e1) {
            a = errCall(e1);
        }
        var b = loginSucc(a);
    } catch(e2) {
        b = loginErr(e2);
    }

You can break out of the chain by calling

return $q.reject('Reason Err was called');

in your errCall() function.

EDIT: As OP remarked by calling $q.reject the code will enter the loginErr function. Alternatively you can modify your code like this:

Auth.signup()
.then(function(a) {
    succCall()
    return loginSucc(a).then(null, loginErr);
}, errCall)

You can read more in these two SO question:

  1. Break promise chain
  2. Break Out of then promises in Angularjs

This also is a helpful read : Flattening Promise Chains

Alee answered 21/12, 2015 at 15:58 Comment(8)
your answer isn't really accurate in the sens that it doesn't break then chain, it goes to the second error, which is what i don't want. " I want to break out of the then chain when any errorCallback (which is errCall or loginErr here) is encountered. "Lotson
yeah this is what i dont want, to start a pyramid style promise pattern but i guess there's no promise method to break like in loops or in switch syntaxLotson
I havn't found a diecent way, no. You can always use goto -I am joking!Alee
@Lotson what i usually do to solve that is use a catch at the end and avoid handling errors in the middle. If any one gets rejected halfway through, it'l go to the catch. The error filter that you're passing to .then is there so that you can turn it into a success or a better error for the next thing in the chain.Sontich
@KevinB in the case of i signup then an auto login for exemple, like here, if i don't have two distinct error handlers i can't render on my page the appropriate raction i want fore each of them. Now if i have to do some if -> else in the only error handler i have to determin which action to do, there's no use for me to use promises, i could use callbacks or gyosifov's methodLotson
If both cases result in rendering a message to the user, it would make even more sense to use .catch, combined with .then error filter. Have .then morph the error to the message you want to render, then render it in .catch, that makes the code dryer. renderError(message)Sontich
@KevinB this means putting in one catch, the logic of many different things which is really untidy, to have on catch which does "if(you are logi error) else if (you are signup error) else if (your are...)" it's pretty ugly to see, this is what i want to avoidLotson
Depends on whether they all do x, or if each results in something completely different. Usually presenting an error message is pretty standard and would not need if else logic.Sontich
L
1

Just don't pass any errCall or loginErr to then and use catch() in the end of the promise chain and it will be interrupted on first error, which will be passed to catch(). If you want explicitly process error of Auth.signup() then your errCall should look like this:

function (err) {
  if(isFatal(err)) {
    return Promise.reject(new Error('Fatal!')); //`catch()` handler will be called with `new Error('Fatal!')`
  } else {
    return 'something'; //next `then()` handler will be called with 'something'
  }
}
Lemke answered 21/12, 2015 at 15:49 Comment(3)
this means i have to check in each success or resolve callback a value to check if the previous one failed, which is pretty ugly i findLotson
No, you don't have to check each callback value, unless you want explicitly process particular promise fail result.Lemke
your method can't allow me to handle each then errors seperatly, which is what i was seeking for but i found the soulution thanks.Lotson
R
0

Your best option is to return a promise that is never resolved from errCall():

function errCall() {
     console.log('errCall');
     return $q.defer().promise;
}

But you are right, what you are trying to do is "against the philosophy of promises".

Why you shouldn't do it

It is of course a problem that your loginSucc function is called when an error occurs during evaluation of the first promise. However, that can be fixed by rejecting once again in errCall as others have already pointed out. But you should not try to avoid the execution of loginErr, otherwise something is conceptually wrong with your code.

The evaluation of the expression

Auth.signup().then(succCall, errCall)

yields another promise. The contract is that a promise has a then() method taking two callbacks as parameters, one for success and one for failure. The contract is also that the success callback is called when the promise is evaluated successfully and that the error/failure callback is called in all other cases. If you intend to never call either of those, don't use a promise!

Ratafia answered 21/12, 2015 at 17:55 Comment(3)
something is wrong in what you said, i want on of these to be run Auth.signup().then(succCall, errCall) but i don't want the next "then" Auth.signup().then(succCall, errCall).then(loginSucc, loginErr) to be run because the previous was rejected that's what i'm trying to acheive, but yeah it may be against the philosophy of promisesLotson
I understand your problem. I updated my answer, so I hope now it is clear what I mean.Ratafia
thanks, i will try this, i found out that doing $q(function(){return null;}) cancels also the promise proppagation blog.zeit.io/…Lotson

© 2022 - 2024 — McMap. All rights reserved.