5

I have come across multiple applications where using catch is preferred over rejectHandler. Eg: Preferring

new Promise.then(resolveHandler).catch()

instead of

new Promise().then(resolveHandler, rejectHandler).catch()

Is there a particular reason for this??

I find

new Promise().then(resolveHandler, rejectHandler).catch()

to be more useful because

  1. I can use rejectHandler to address designed/expected error scenario where Promise.reject is called.
  2. I can use catch block to address unknown/unexpected programming/runtime errors that occur.

Does someone know any particular reason why rejectHandler is not used much?

P.S. I am aware of newer alternatives in ES6 but I just curious to know this.

Update: I KNOW HOW rejectHandler and catch works. The question is why do I see more people use only catch over both rejectHandler and catch? Is this a best practice or there is some advantage?

Update(Adding answer here): Found the answer I was looking for first hand. The reason is not just because the error in reject is handled by catch it is mainly because of chaining. When we are chaining promise.then.then.then.then, having a resolve, reject pattern proves a bit tricky to chain it since you wouldn't want to implement a rejecthandler just to forward the rejectData up the chain. Using only promise/then/catch along with resolve/return/throw proves very useful in chaining N numbers of thenables. @Bob-Fanger(accepted answer) addressed some part of this too. Eg:

getData(id) {
        return service.getData().then(dataList => {
            const data = dataList.find(data => {
                return data.id === id;
            });
            if (!data) {
                // If I use Promise.reject here and use a reject handler in the parent then the parent might just be using the handler to route the error upwards in the chain
              //If I use Promise.reject here and parent doesn't use reject handler then it goes to catch which can be just achieved using throw.
                throw {
                    code: 404,
                    message: 'Data not present for this ID'
                };
            }
            return configuration;
        });
    }


//somewhere up the chain
....getConfiguration()
            .then(() => {
                //successful promise execution
            })
            .catch(err => {
                if (err.code) {
                    // checked exception
                    send(err);
                } else {
                    //unchecked exception
                    send({
                        code: 500,
                        message: `Internal Server error: ${err}`
                    });
                }
            });

Using just these All I need to worry about is promise/then/catch along with resolve/return/throw anywhere in the chain.

Cœur
  • 37,241
  • 25
  • 195
  • 267
wallop
  • 2,510
  • 1
  • 22
  • 39
  • 1
    *"Is this a best practice or there is some advantage?"* Just have to write one function instead of two? I don't think a best practice has established around that. I think people just find this easier to understand. – Felix Kling Oct 04 '18 at 23:01
  • 1
    *"Is this a best practice or there is some advantage?*" No technical advantage whatsoever (and thus, no best practice). As I said in my answer, it really depends on what you need the code to achieve. – Adam Jenkins Oct 04 '18 at 23:04
  • *P.S. I am aware of newer alternatives in ES6 but I just curious to know this.* - what are the newer alternatives? – Adam Jenkins Oct 04 '18 at 23:05
  • 1
    personally, I have used the `promise.then(resolveHandler, rejectHandler).catch(catchHandler)` "pattern" on the single occasion where it actually fit with the requirements. Then I realised the requirements needed re-visiting and don't use that pattern any more – Jaromanda X Oct 04 '18 at 23:18
  • I'm quite sure there are a couple duplicates of this here as this has certainly been asked before. I will try to find them. The main difference is that the `.catch()` handler will also catch any exceptions or rejections within the `resolveHandler`, but the `rejectHandler` will not. – jfriend00 Oct 05 '18 at 00:55
  • @jfriend00 yes I know that. Also I did search google to find this question but all questions were for specific scenarios rather than a use case – wallop Oct 05 '18 at 01:27
  • I dont see how that is a duplicate??? The link suggests using rejectHandler OR catch. My questions is rejectHandler with catch vs rejectHandler without catch – wallop Oct 05 '18 at 16:41
  • Update: sorry I meant to say my question is between "catch with rejectHandler vs catch without rejectHandler" which is different to the duplicate link pointed out. – wallop Oct 09 '18 at 16:13

3 Answers3

7

The difference is that if an error occurs inside resolveHandler it won't be handled by the rejectHandler, that one only handles rejections in the original promise.

The rejectHandler is not used in combination with catch that much, because most of the time we only care about that something went wrong.
Creating only one errorhandler makes the code easier to reason about.

If a specific promise in the chain should handled differently that can be a reason to use a rejectHandler, but i'd probably write a catch().then().catch() in that case.

Bob Fanger
  • 28,949
  • 7
  • 62
  • 78
  • 2
    Interesting explanation. Also regarding to your point, "The rejectHandler is not used in combination with catch that much, because most of the time we only care about that something went wrong.", true we only think of what went wrong although I have realized over time it is good to consider if it was an expected wrong or unexpected one – wallop Oct 05 '18 at 01:29
6

Neither is more useful than the other. Both the rejected handler and the catch callback are called when an error is thrown or a promise is rejected.

There is no "best practice" to use one over the other. You may see code use one or the other, but it's use will be based on what the code needs to achieve. The programmer may want to catch an error at different times in the chain and handle errors thrown at different times differently.

Hopefully the following will help explain what I mean:

somePromise
  .then(
      function() { /* code when somePromise has resolved */ },
      function() { 
        /* code when somePromise has thrown or has been rejected. 
        An error thrown in the resolvedHandler 
        will NOT be handled by this callback */ }
   );

somePromise
  .then(
      function() { /* code when somePromise has resolved */ }
   )
   .catch(
      function() { 
        /* code when somePromise has thrown or has been rejected OR 
        when whatever has occurred in the .then 
        chained to somePromise has thrown or 
        the promise returned from it has been rejected */ }
   );

Notice that in the first snippet, if the resolved handler throws then there is no rejected handler (or catch callback) that can catch the error. An error thrown in a resolved callback will not be caught by the rejectedHandler that is specified as the second argument to the .then

sr9yar
  • 4,850
  • 5
  • 53
  • 59
Adam Jenkins
  • 51,445
  • 11
  • 72
  • 100
  • Thank you. "There is no "best practice" to use one over the other. You may see code use one or the other, but it's use will be based on what the code needs to achieve." This explains – wallop Oct 05 '18 at 01:31
0

As stated in the post, provision of a resolve and reject handler in the same call to .then allows dealing with rejection of the previous promise separately from errors thrown within, or returning a rejected promise from, the success handler.

Because a rejection handler returning without throwing an error resumes the fufilled channel of a promise chain, a final catch handler will not be invoked if a previous rejection handler returns normally.

The question then devolves into use cases, cost of development and level of knowledge.

Use cases

In theory the two parameter form of then call could be used to retry an operation. But because hard coded promise chains are set up statically, retrying the operation is not simple. An easier way to retry might be to use an async function with try-catch statements surrounding await of a promise that may need to be retried as in this concept code:

async function() {
    let retries = 3;
    let failureErr = null;
    while( retries--) {
       try {
          var result = await retryableOperationPromise() 
          return result;
       }
       catch( err) {
          failureErr = err;
       }
     }
     throw failureErr // no more retries
}

Other use cases may not be widespread.

Cost of development or commercial decisions.

If telling a user to retry later is acceptable it may be cheaper than doing anything about specific reasons for promise rejection. For example if I try to book a flight over midnight, when airlines put the prices up, I usually get told "an error occurred, try again later" because the price I was given at the start of booking will not be honored.

Knowledge (<opinion>)

I suspect that promise usage may often be based on example rather than in-depth knowledge of the subject. It is also possible program managers want to keep the code base as simple as possible for less experienced developers (probably a cost issue).

"Best practice" may not truly apply for making decisions on how to use promises if the usage is valid. Some developers and managers will avoid some forms of usage as a matter of principle, but not always based on technical merit.

Community
  • 1
  • 1
traktor
  • 17,588
  • 4
  • 32
  • 53