Recall that each then
in the chain defines a Promise
, and they are processed in order. In the "happy case" there are no errors and the result of each onFulfilled
handler is passed as an argument to the next Promise
.
Similarly, if an error occurs – either by an explicit Promise.reject
or a throw
– then the error object is passed as an argument to the onRejected
handler of the next Promise
.
If an onRejected
handler returns a value then it will be passed onto the next onFulfilled
handler, i.e. the error is regarded as "taken care of"; if, however, it rejects or re-throws the error then that will be passed to the next onRejected
handler.
The link you've provided clarifies that the default onRejected
handler is function ((x) => { throw x; })
– that is, if you don't define your own handler then any error from earlier in the chain is re-thrown. So, the default behaviour is that the error is just passed down the chain until it gets to the end.
Recall that .catch(handler)
is just syntactic sugar for .then(undefined, handler)
. There's nothing special about it being at the end – you can put .catch(..)
handlers in the middle of the chain – but if some Promise
in the chain throws an unhandled error then you see the familiar Uncaught (in Promise)
message / error in the console.