Placement of catch BEFORE and AFTER then
Asked Answered
C

3

179

I have trouble understanding the difference between putting .catch BEFORE and AFTER then in a nested promise.

Alternative 1:

test1Async(10).then((res) => {
  return test2Async(22)
    .then((res) => {
      return test3Async(100);
    }).catch((err) => {
      throw "ERROR AFTER THEN";
    });
}).then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
});

Alternative 2:

test1Async(10).then((res) => {
   return test2Async(22)
     .catch((err) => {
        throw "ERROR BEFORE THEN";
      })
      .then((res) => {
        return test3Async(100);
      });
  }).then((res) => {
    console.log(res);
  }).catch((err) => {
    console.log(err);
  });

The behavior of each function is as follow, test1 fail if number is <0 test2 fails if number is > 10 and test3 fails if number is not 100. In this case test2 is only failing.

I tried to run and make test2Async fail, both BEFORE and AFTER then behaves the same way and that is not executing the test3Async. Can somebody explain to me the main difference for placing catch in different places?

In each function I console.log('Running test X') in order to check if it gets executed.

This question arises because of the previous thread I posted How to turn nested callback into promise?. I figure it is a different problem and worth posting another topic.

Creamcolored answered 2/2, 2017 at 21:59 Comment(6)
both .then and .catch can alter the promise... so i'm not sure where the mis-understanding comes from. If you put catch before the .then, it'l catch rejections that happened before the .then and the .then will run it's done/fail callbacks based on what happens within the .catch, and vice versa when you swap them.Consternate
Sorry if my question was not clear. But in this case as I said, both cases behave the same so I cannot see the difference. Can you tell me when do we put catch BEFORE and when we decided to put it AFTER then? putting it after seems really intuitive and common. Just not sure why sometimes we put it before thenCreamcolored
If they perform the same, it's simply because what each does isn't altering the outcome in this specific case. A minor change to either could alter the outcome.Consternate
What do you mean "altering the outcome ". Sorry I am really confuse hahaCreamcolored
For example, if instead of throwing an error you just did nothing, the promise would switch from being rejected to being resolved. That would of course alter the outcome, because the promise is now a resolved promise rather than a rejected one. (unless of course it was already resolved, in which case the catch wouldn't have run anyway)Consternate
In the case in your question, the promise is rejected, therefore the .then's done callback won't get called, and it skips forward to the .catch. When the two are swapped, the .catch runs, and then throws an error thus keeping it rejected, and the .then's done callback again doesnt run for the same reason as in the first case.Consternate
I
378

So, basically you're asking what is the difference between these two (where p is a promise created from some previous code):

return p.then(...).catch(...);

and

return p.catch(...).then(...);

There are differences either when p resolves or rejects, but whether those differences matter or not depends upon what the code inside the .then() or .catch() handlers does.

What happens when p resolves:

In the first scheme, when p resolves, the .then() handler is called. If that .then() handler either returns a value or another promise that eventually resolves, then the .catch() handler is skipped. But, if the .then() handler either throws or returns a promise that eventually rejects, then the .catch() handler will execute for both a reject in the original promise p, but also an error that occurs in the .then() handler.

In the second scheme, when p resolves, the .then() handler is called. If that .then() handler either throws or returns a promise that eventually rejects, then the .catch() handler cannot catch that because it is before it in the chain.

So, that's difference #1. If the .catch() handler is AFTER, then it can also catch errors inside the .then() handler.

What happens when p rejects:

Now, in the first scheme, if the promise p rejects, then the .then() handler is skipped and the .catch() handler will be called as you would expect. What you do in the .catch() handler determines what is returned as the final result. If you just return a value from the .catch() handler or return a promise that eventually resolves, then the promise chain switches to the resolved state because you "handled" the error and returned normally. If you throw or return a rejected promise in the .catch() handler, then the returned promise stays rejected.

In the second scheme, if the promise p rejects, then the .catch() handler is called. If you return a normal value or a promise that eventually resolves from the .catch() handler (thus "handling" the error), then the promise chain switches to the resolved state and the .then() handler after the .catch() will be called.

So that's difference #2. If the .catch() handler is BEFORE, then it can handle the error and allow the .then() handler to still get called.

When to use which:

Use the first scheme if you want just one .catch() handler that can catch errors in either the original promise p or in the .then() handler and a reject from p should skip the .then() handler.

Use the second scheme if you want to be able to catch errors in the original promise p and maybe (depending upon conditions), allow the promise chain to continue as resolved, thus executing the .then() handler.

The other option

There's one other option to use both callbacks that you can pass to .then() as in:

 p.then(fn1, fn2)

This guarantees that only one of fn1 or fn2 will ever be called. If p resolves, then fn1 will be called. If p rejects, then fn2 will be called. No change of outcome in fn1 can ever make fn2 get called or vice versa. So, if you want to make absolutely sure that only one of your two handlers is called regardless of what happens in the handlers themselves then you can use p.then(fn1, fn2).

Iover answered 3/2, 2017 at 16:29 Comment(10)
The question is specifically about the order of .then() and .catch(), which you answer. In addition you give some tips of when to use which order, where I think it's appropriate to mention a third option, namely passing both the success and error handler to .then(). In that case at most one handler will be called.Precept
@Precept - Good suggestion. I added.Iover
So, during Promise Chaining can we write .then .catch .catch .then kind of scenarios?Newspaper
@KapilRaghuwanshi, yes, you can use it to pass a default value in case of failure. ie Promise.reject(new Error("F")).then(x => x).catch(e => {console.log(e); return [1]}).then(console.log) and Promise.resolve([2]).then(x => x).catch(e => [1]).then(console.log)Ashmead
To clarify, when p rejects in the second scheme, even if the catch returns nothing, the error is considered handled and the promise chain switches to a resolved state to call further then()s.Ramage
Debugging something like this now. Scenario 2 return p.catch(...).then(...);. The code jumps to then first, and then to catch an error from p. ES6 Promises. Order of then and catch doesn't seem to matter. And it doesn't matter if catch throws, since we get there only after then.Harebrained
@DmitryShvedov - What you're saying doesn't make sense to me so we'd have to see your actual code to understand better. If you need help, I'd suggest you write your own question and show the relevant code. If you leave a comment here with a link to the new question, I'll take a look. My guess is that you're not passing a function reference to .then() (something that .then() can call later).Iover
@Iover I settled for "The other option", that works for me, so not worth a separate question. My code was literally this: onDelete(service).catch((error) => {this.setState({error: error});}).then(this.setState({isModalOpen: false})); When onDelete erred, debugger jumped straight to then and catch after.Harebrained
@DmitryShvedov - As I guessed, this is wrong .then(this.setState({isModalOpen: false})). You're not passing a function reference to .then() so that code in the parens is executed immediately(before the promise resolves). It should be .then(() => this.setState({isModalOpen: false})).Iover
@Iover You're right! Such an obvious bug. Thanks for the explanation.Harebrained
F
48

jfriend00's answer is excellent, but I thought it would be a good idea to add the analogous synchronous code.

return p.then(...).catch(...);

is similar to the synchronous:

try {
  iMightThrow() // like `p`
  then()
} catch (err) {
  handleCatch()
}

If iMightThrow() doesn't throw, then() will be called. If it does throw (or if then() itself throws), then handleCatch() will be called. Notice how the catch block has no control over whether or not then is called.

On the other hand,

return p.catch(...).then(...);

is similar to the synchronous:

try {
  iMightThrow()
} catch (err) {
  handleCatch()
}

then()

In this case, if iMightThrow() doesn't throw, then then() will execute. If it does throw, then it would be up to handleCatch() to decide if then() is called, because if handleCatch() rethrows, then then() will not be called, since the exception will be thrown to the caller immediately. If handleCatch() can gracefully handle the issue, then then() will be called.

Farrison answered 7/11, 2017 at 5:58 Comment(3)
this is good explanation but you could wrap the orphan then() in a finally{...}Malave
@82Tuskers, Are you sure? If I put then() in finally{...}, wouldn't it incorrectly be called even if handleCatch() throws? Keep in mind that my goal was to show analogous synchronous code, not to suggest different ways of handling exceptionsFarrison
So, if we want to handle all cases but still chain .then() would it be best to use .then(do something).catch(log err and update state).then(do another thing).catch(log err) where we attempt to catch at every point but also continue to execute the stmnts?Oceanic
C
0

Some great answers, however didn't explicitly tell me what I was looking for, and therefore am adding.

I had wanted to know, that once the catch block was invoked and it returned some value, then how would the remaining .then behave.

So the .catch works exactly as the .then in the chain, and any value returned by the .catch gets processed by the following .then steps as normal. i.e. for a following .then its just as if there was a preceding .then which returned the value.

This means in the .catch one can return an error version e.g. a default value, which would get processed as normal by all the subsequent .then to return an error version or default of the value.

const basePromise = new Promise((resolve, reject) => {
    throw new Error(10);
    //resolve(100);
    //reject("Ooops slipped up");
});

const myDataCall = () => basePromise.
                            catch((reason) => {
                                //console.log(reason);
                                return 500;
                            }).
                            then(x => x * 2).
                            then(x => x + 50);



const myFunc = async () => {
    const result = await myDataCall();
    console.log(`The result was: ${result}`);
}

myFunc();

This will print out: The result was: 1050.

Craver answered 30/11, 2023 at 12:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.