3

In JavaScript, if I have try catch blocks in a function that is meant to be called from another function, should I also put them in the calling function or just let the called functions handle them.

Here is some pseudo code to illustrate what I mean. I have two called functions, one and two. They are essentially the same but each calls a different API and throws an error if something goes wrong. Then I have two versions of a third function, one with a try-catch block and one without.

  • Is one of these(function threes) a better practice than the other?
  • Is there a still better way to do it?
 function one async (){        
        try{
            //await some value
              // return the value
            
            // if that doesn't work throw an error
        } catch(error){
            //handle error
        }
    }
    
    function two async (){        
        try{
            //await some value
              // return the value
            
            // if that doesn't work throw an error
        } catch(error){
            //handle error
        }
    }
    
    
    function three-version-1 (){       
        try{
            // await return value from one()
                // do something with it
        
            // if that doesn't work await return value from two()
                // do something with that instead
            
            // if that doesn't work throw an error
        } catch(error){
            //errors are handled in called functions but put handle them here anyway just in case
        }
    }
    
    
    function three-version-2 async (){
        // await return value from one()
           // do something with it
        
        // if that doesn't work await return value from two()
           // do something with that instead
          
        // errors are handled in called functions so no need to handle them here
    }
2
  • Are the catch blocks re-throwing any of the exceptions? Jun 16 at 16:44
  • In this context, One() and Two() catch blocks would essentially just terminate the function and log a message that something went wrong. Then javascript would implicitly return a value of 'undefined' to the calling function. Since 'undefined' would get coerced into a 'false' in a conditional, Three-v1() would throw a new, entirely different error and log something to the effect "that other error originated here" whereas Three-v2() would not throw an error and just display a message saying something to the effect of "no soup for you!"
    – Russ Bain
    Jun 16 at 23:41
13

You should only catch when you can actually do something to resolve the error.

So, you normally always catch, or otherwise handle exceptions at the top level, because you want to log or display the error somehow even if you can't resolve it.

Don't just put catch { throw } in all your functions, let the exception bubble up.

I would put a catch in functions for something like...

try 
{
   //load data over dodgy IR connection
}
catch(TransmissionException)
{
   //retry because we expect exceptions and know they are temporal
}

Or this

try
{
   //something complicated
}
catch
{
   throw //don't know what to do with errors but need a catch syntactically
}
finally
{
    //need to do some clean up
}

But not:

SavetoDisc()
{
    try
    {
       //write to disc
    }
    catch(RunOutOfDiscSpace e)
    {
        //log an error (to disc?)
        throw //only using catch block to log
    }
}

Main()
try
{
    SaveToDisc()
}
catch
{
   //log the error!
   throw (again?)
}
8
  • 1
    @JimmyJames dont i say that in sentence 2?
    – Ewan
    Jun 16 at 22:23
  • @Ewan I'm not clear on what you are saying. There seems to be some implicit context in your answer that is clear to those at a higher level of understanding than where I am right now but not to me. Citing my pseudo-code example in the OP, would you say that I should leave function three-version-1 as is, but remove the try catch from function one and function two - having them throw the errors but letting function three catch the errors?
    – Russ Bain
    Jun 17 at 0:34
  • 1
    @RussBain It might help if you fleshed out that code with some real code. It's not totally clear what you are getting at in those examples.
    – JimmyJames
    Jun 17 at 13:34
  • 1
    @RussBain sorry, the problem with your pseudo code is that whether you handle the error in the function or not depends on what the error is and what the function does. My examples for disc space error and transmission error are syntactically identical, but I'm saying do one and not the other because with a transmission error retrying may fix the problem, but it wont help with running out of disc space. Having said that you could rejig the example and say when you get the disc-space error, delete some other file and save again and use it as the "DO" example.
    – Ewan
    Jun 17 at 21:47
  • 1
    I did some playing around and made a beginner friendly gist that demonstrates what @Ewan explained about only catching at the top. One of the things my gist demonstrates is that putting try-catch blocks in the middle may not do anything at all except clutter up your code. This can be shown by reverse-commenting the 'result' variable in the 'topLevel' function. To get different results just pass a false or true into the 'topLevel' function. gist.github.com/TwoFistedJustice/…
    – Russ Bain
    Jun 19 at 23:18
1

One() and Two() catch blocks would essentially just terminate the function and log a message that something went wrong.

This is almost never what you want to do. Normally, when you call a function you either are expecting a result or you are expecting some sort of side-effect (or both.) When you just log and exit it creates issues in both scenarios.

If your function returns a value, you need to return something to the caller. There's an alternative to exceptions using a return value that may represent an error but I'm going to ignore that option in this answer. So generally, you'll end up returning null or some other non-answer. Now you need to accommodate the possibility of this in every call to a function that might terminate abnormally. This tends to be a mess and often becomes a source of defects.

If there's some expectation of some side-effect, it's usually the case that later steps in your application depend on that being completed. If you just exit the function when something goes wrong, your caller will not have any indication that it didn't happen. In practice this means something else fails later, or worse, something happens later that puts your things into an invalid state.

The concept of an exception is that it's an alternative path to execution flow. As an analogy, let's say you are writing a function that engages the landing gear of a plane. What do you do if the doors don't open? You don't just exit and log to the flight recorder so that the cleanup crew can find it. It's important that the landing gear sequence stops and this problem reported back to the cockpit so that the pilots can do something about it. It doesn't really matter which way it failed, you want to do the same thing: signal that there was an issue and what is known about it regardless of which function in that sequence failed.

This brings us back to ewan's answer. Don't catch exceptions that you can't resolve. Doing so defeats the purpose. The point is to let them bubble up to a point in your application where they can be handled appropriately. Often that means exiting abnormally and displaying an error message. By halting that bubble up routine prematurely, you aren't helping anyone, it just makes things worse.

3
  • I actually entirely rewrote the question and the example code based on previous comments. But you answered my question and someone upvoted it before I finished the rewrite. Would it be appropriate to post it as an "answer" even though it is really a better phrased version of the original question? And also, JavaScript functions always return something, even if you don't explicitly tell them to. They have a default return value of 'undefined', which can easily by coerced into a 'false' for use in a conditional.
    – Russ Bain
    Jun 17 at 21:10
  • @RussBain I upvoted it mainly because if I think if I'm answering a question, I think should upvote it. A complete rewrite can be seen as bad form. If the existing answers won't make sense based on your rewrite, then I think you should add an answer instead.
    – JimmyJames
    Jun 17 at 21:15
  • New "answer" posted. I hope the refactored version provides better clarity.
    – Russ Bain
    Jun 17 at 22:22
0

[this is a rewrite of the original question based on feedback from @JimmyJames and @Ewan it was suggested that I post a new 'answer' rather than completely refactor the OP]

(my intention here is to derive a general-princicple/best-practice)

What is the best way to handle errors with try-catch blocks in this situation?

In JavaScript, I have a backend function that asynchronously fetches data from an external API. That function will be called by and return a value to a frontend function.


Pathway to Truth:

The user enters a text value into an input box in a web page, a place or product name for example. The application then sends that value to an external server to fetch desired information. Upon receipt of the information, the webpage displays the relevant bits to the user. If there is an error, the application resets and notifies the user to try again.

Conventions Used;

  • Backend functions meant to be called by other functions have numeric indicators.
  • Frontend functions meant to do the calling have alphabetic indicators
  • Rather than write eight nearly identical example blocks of code, I took a few syntactical liberties

Greater Assumptions:

  • Javascript functions implicitly return a falsy value of 'undefined' if not explicitly told to return a value.
  • Any exception is due to an external server not responding with useful data, which could be due to the user providing useless input, or something going wrong down the line. In that event, the application will continue to run and the user will get a "try again" message.

Lesser Assumptions:

  • Since 'A' has error handling it Can 'safely' call either function 'One' or function 'Two'.
  • Since 'B' has no error handling, it would only call function 'One'.

The choices here are:

  • 'A' calls 'One' (both have their own try-catch)
  • 'A' calls 'Two' (only 'A' has try-catch)
  • 'B' calls 'One' (only 'One' has try-catch)

Questions:

Should I put try-catch blocks in both functions or just in one?

If only one, which one?

What would be a best-practice here?

Is there a better way?


someBackendFile.js

export async function One async (){    
    // errors are handled here regardless of whether they are handled in the calling function
    
        try{
            let httpResponse = await fetch('someURL' {someOptions});
              if (!httppRespons.ok) {
                  throw new Error('oops!');
            }
            
        return await httpResponse.json;
        
        } catch(error){
            // try again n times 
            // then alert someone that a problem happened
        }
    }
    

export async function Two async (){
        // errors will bubble up and be handled by calling function
    
        let httpResponse = await fetch('someURL' {someOptions});
        return await httpResponse.json;       
    }

someFrontEndFile.js

import{One, Two} from 'someBackendFile'

async function A (someparam){
    // errors are handled here regardless of whether they are handled in the called function
    
        try
                  // "***" (syntactical liberty), indicates either "One" or "Two" may be called
            let desiredInfo = await ***(someparam);
            
            // if called function returns a falsy value
            if (!desiredInfo) {                
                throw new Error('oops again! - that other oops originated here.');
            }
            return desiredInfo;
            
        } catch(error){
            // if calling fn Two, try again n times
            // alert someone that a problem happened
        }
    }
    
    
async function B (someParam){
    // errors are handled in called function so no need to handle them here.
    
        let desiredInfo = await One(someparam);
        return desiredInfo;
    }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.