4

When working with promises in JavaScript, we have a possibility to use .then, .catch and .finally. Each of these methods returns a new Promise object.

Using .then is pretty straightforward - we chain them. The use case for finally - is to put it at the end of the chain of .then and .catch. But

In the code below, as I understand, we initialize promise p which can resolve or reject. I could use .then(resolutionHandler, rejectionHandler), which would be self-explanatory as it's only 1 .then "handler" with both handlers, but in case of sequencing .then and .catch instead of the latter approach-

**Are .then and .catch handlers somehow paired and treated like .then(resolutionHandler, rejectionHandler)? or something else is happening? **

const p = new Promise((resolve, reject) => {
  reject("ups...");
});

p
  .then(data => {
    // this is success handler for Promise "p"
  })
  .catch(err => {
    // Is this failure handler for Promise "p"?
  })
zmii
  • 4,123
  • 3
  • 40
  • 64

2 Answers2

3

Not exactly. When you have p.then(handleThen).catch(handleCatch), if p rejects, handleCatch will handle it. But handleCatch will also handle errors thrown by handleThen.

Although those sorts of errors are pretty unusual with if handleThen contains only synchronous code, if handleThen returns a Promise, handleCatch will be able to handle that Promise if it rejects.

<somePromiseChain>
  .catch(handleCatch);

will have the handleCatch handle any errors thrown anywhere in the above Promise chain.

In contrast, with p.then(resolutionHandler, rejectionHandler), the rejectionHandler will only handle a rejection of p. It will completely ignore anything that occurs in resolutionHandler.

If the resolutionHandler is completely synchronous, doesn't return a Promise, and never throws (which is pretty common), then .then(resolutionHandler, rejectionHandler) is indeed equivalent to.then(resolutionHandler).catch(rejectionHandler). But it's usually a good idea to use .then(..).catch(..).

With p.then(success).catch(fail), the .catch is actually attached to the Promise returned by the .then - but if p rejects, the .then's Promise rejects as well, with the same value as p's rejection. It's not that the catch is attached directly to p, but it's attached to a .then which passes p's rejection through.

Every .then / .catch is attached directly to the upper Promise it's called on, but sometimes that upper Promise passes through the value from its upper Promise unaltered. That is. p.then(undefined, fail).then(success) will run success if p resolves, with the intermediate .then passing the resolution through. With rejection, p.then(success).catch(fail) will run fail because the .then(success) passes the failure from p through unaltered.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • So these are 2 completely different mechanisms and they are not resolved to same code by JS runtime. I may suspect that in case `p.then.catch` if `p` get's rejected, then first `.then` will be tested to see if there is `rejectionHandler`, it's not there in `.then.catch` case, so rejection propagates to the next handler (basically promise-return-value of first .`then` is rejected in this case), whatever this is `.then`, `.catch` or `.finally`... is this right? If yes, where is this written in JS documentation? – zmii Apr 28 '20 at 10:53
  • 1
    Yes, if there's `p.then(success, fail)`, the `fail` will handle errors from `p`, no matter what comes after the whole `.then`. With `p.then(success, fail)`, either `success` or `fail` will be entered into regardless. But `.then(success)` only runs if the upper Promise resolved, and `.catch(fail)` only runs if the upper Promise rejected. – CertainPerformance Apr 28 '20 at 10:56
  • 1
    You can see the specification on it [here](https://tc39.es/ecma262/#sec-performpromisethen), but it's quite dense. – CertainPerformance Apr 28 '20 at 10:57
  • Another way of looking at it: `.catch(fail)` does the same thing as `.then(undefined, fail)`. If the upper Promise rejected, `fail` is entered, otherwise the fulfilled Promise passes through unaltered. Similarly, `.then(success)` is passed through if the upper Promise rejected. – CertainPerformance Apr 28 '20 at 11:03
  • An if `p` throws an `error` or `rejects` and we have the following code `p.then.catch`, this means `.catch` is attached to Promise returned by `.then`, right? This means that `.then` can't handle rejection of `p` will make return value of itself - Promise in rejected state. Yes? So `.catch` doesn't react upon `p` being rejected, but on `p.then` being rejected. – zmii Apr 28 '20 at 11:07
  • 1
    With `p.then(success).catch(fail)`, the `.catch` is actually attached to the Promise returned by the `.then` - but if `p` rejects, the `.then`'s Promise rejects as well, with the same value as `p`'s rejection. It's not that the `catch` is attached directly to `p`, but it's attached to a `.then` which passes `p`'s rejection through. – CertainPerformance Apr 28 '20 at 11:09
  • Please add your last comment to the body of your answer as this was the underlying thing I was asking about. Thank you for help! – zmii Apr 28 '20 at 11:13
1

Calling catch internally calls reject

As per mdn

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.then(undefined, onRejected)).

brk
  • 48,835
  • 10
  • 56
  • 78