I\'m trying to understand Promises from the MDN documentation. The first example demonstrates the then
and catch
methods:
// We def
First, a little background on how the relevant parts of promises work:
p1.then(...)
does return a new promise that is chained to the previous one. So, p1.then(...).then(...)
will execute the second .then()
handler only after the first one has finished. And, if the first .then()
handler returns an unfulfilled promise, then it will wait for that returned promise to resolve before resolving this second promise and calling that second .then()
handler.
Secondly, when a promise chain rejects anywhere in the chain, it immediately skips down the chain (skipping any fulfilled handlers) until it gets to the first reject handler (whether that comes from a .catch()
of from the second argument to .then()
). This is a very important part of promise rejection because it means that you do not have to catch rejections at every level of the promise chain. You can put one .catch()
at the end of the chain and any rejection that happens anywhere in the chain will go directly to that .catch()
.
Also, it's worth understanding that .catch(fn)
is just a shortcut for .then(null, fn)
. It works no differently.
Also, keep in mind that (just like .then()
) a .catch()
will also return a new promise. Any if your .catch()
handler does not itself throw or return a rejected promise, then the rejection will be considered "handled" and the returned promise will resolve, allowing the chain to continue on from there. This allows you to handle an error and then consciously decide if you want the chain to continue with normal fulfill logic or stay rejected.
Now, for your specific questions...
If so, then wouldn't that mean that the catch callback would be called only if the promise returned from p1.then, not the promise p1, resolved to rejected? And wouldn't I have to do this:
No. Rejections propagate immediately down the chain to the next reject handler, skipping all resolve handlers. So, it will skip down the chain to the next .catch()
in your example.
This is one of the things that makes error handling so much simpler with promises. You can put .catch()
at the end of the chain and it will catch errors from anywhere in the chain.
There are sometimes reasons to intercept errors in the middle of the chain (if you want to branch and change logic on an error and then keep going with some other code) or if you want to "handle" the error and keep going. But, if your chain is all or nothing, then you can just put one .catch()
at the end of the chain to catch all errors.
It is meant to be analogous to try/catch blocks in synchronous code. Putting a .catch()
at the end of the chain is like putting one try/catch block at the highest level around a bunch of synchronous code. It will catch exceptions anywhere in the code.
All three work. I can understand the latter two. The gist of my question is, why does the first approach also work?
All three are pretty much the same. 2 and 3 are identical. In fact, .catch(fn)
is nothing more than a shortcut for .then(null, fn)
.
Option 1 is slightly different because if the onFulfilled
handler throws or returns a rejected promise, then the .catch()
handler will get called. In the other two options, that will not be the case. Other than that one difference, it will work the same (as you observed).
Option 1 works because rejections propagate down the chain. So, if either p1 rejects or if the onFulfilled handler returns a rejected promise or throws, then the .catch()
handler will be called.
This:
var p2 = p1.then()
p2.catch()
is the same as this:
p1.then().catch()
You can also do this:
p1
.then(response => response.body)
.then(body => JSON.parse(body))
.then(data => console.log(data))
.catch(e => console.log('something somewhere failed'))
shouldn't the codes be equivalent
They are.
If so, then wouldn't that mean that the catch callback would be called only if the promise returned from
p1.then
, not the promisep1
, resolved to rejected?
Yes, exactly.
However when p1
rejects, p2
will do so as well, because you did not pass an onRejected
handler to the .then()
call that would have intercepted it. The rejection just propagates down the chain.
I played around in JSFiddle with the three approaches. All three work.
They do, but they don't do the same.
p1.then(onFulfilled, onRejected);
This is what you usually will want to do.
p1.then(onFulfilled); p1.catch(onRejected);
This ends up with two different promises, one that will be resolved with the onFulfilled
result or rejected, and one that will be fulfilled or resolved with the onRejected
result.
p1.then(onFulfilled).catch(onRejected);
Thats's a different beast than the first, see When is .then(success, fail) considered an antipattern for promises?.