The main difference between the forms promise. then(success, error) and promise. then(success). catch(error) is that in case if success callback returns a rejected promise, then only the second form is going to catch that rejection.
catch " around the executor automatically catches the error and turns it into rejected promise. This happens not only in the executor function, but in its handlers as well. If we throw inside a . then handler, that means a rejected promise, so the control jumps to the nearest error handler.
The catch() method returns a Promise and deals with rejected cases only. It behaves the same as calling Promise. prototype. then(undefined, onRejected) (in fact, calling obj. catch(onRejected) internally calls obj.
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.
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.
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.
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.
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)
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With