promise.then(onFulfilled, onRejected) returns a new promise that the handler decides: returning a value fulfills it, returning a promise or thenable makes it follow that one, throwing rejects it, and a missing handler passes the outcome through. catch(onRejected) is shorthand for then(undefined, onRejected), and finally(onFinally) runs on either outcome, receives no argument and passes the value or reason on. An error skips fulfillment handlers until the next rejection handler, so one catch() at the end guards every step.
Promise.resolve(1)
.then(n => { throw new Error(`e${n}`); })
.then(() => console.log("skipped"))
.catch(err => { console.log("caught", err.message); return "fallback"; })
.finally(() => console.log("finally: no arguments"))
.then(value => { console.log("passed through:", value); undefinedFn(); })
.catch(err => console.log("caught", err.name));caught e1 finally: no arguments passed through: fallback caught ReferenceError
The second argument of then() cannot catch an error thrown by the first argument of the same call, hence the usual .then(ok).catch(fail) style. The classic bug is a missing return in a handler: the chain moves on with undefined instead of waiting for the inner promise.