The 'Catches' of Try/Catch in SFMC SSJS


The 'Catches' of Try/Catch in SFMC SSJS

Many people lately have been putting out awesome articles on debugging SFMC SSJS, which is fantastic. Most have a section talking about utilizing try/catch, which is a great tool to help figure out specifically what is wrong in a code block. The issue is, most of these posts leave out some important considerations when using try/catch. I hope to help address those here.

What is Try/Catch?

For those who are unaware of what ‘try/catch‘ is, it is a statement that wraps around a block of code that you will ‘try’ and should any exception or error be thrown, it will abandon that block go to a ‘Catch’ block.

The main benefit here is that it will not default to likely throwing a 500 error, but instead you can gather the error thrown and then elegantly execute other actions or properly display/log this error as you see fit.

Technically, try/catch is not actually the complete form of this statement. There is an optional third clause that can be included – the ‘finally‘ clause. This will execute after the try/catch portion, regardless of which is executed.

So if the try attempt is made, but errors out so catch clause runs, it will then move to finally clause. The same is true if the try succeeds, and the catch is ignored, it will then move to the finally clause and execute there. The finally block will always execute regardless of try/catch success. Note too that by including a Finally clause, you actually make the catch clause optional.

Considerations

My best recommendations when dealing with try/catch:.

  1. Put as little code inside of a try/catch as you can.
  2. Declare global variables outside of blocks, as all variables declared inside are ‘local‘ only.
  3. Use conditionals in your catch block to handle different exceptions/errors in different ways
  4. KISS (Keep it Simple, Stupid). The more complex you make it, the more likely you will need to debug your debugger

The reason I recommend putting as little inside a try/catch block as possible is for ease of reading, maintaining and potential scope issues. This also applies for the KISS recommendation, with the added benefit of the less nested try/catches or complex structures/scopes, the less likely something will go wrong or break. Taking it from a very specific use-case debugging tool to something that may require complex debugging itself.

Try/catch has little to no performance degradation or effect, so implementing it multiple times in different places is not just possible, but also can be recommended. This, of course, comes with the caveat that if the nature of the types of errors you are expecting/handling is singular and easily handled in a single block, then there is no reason to implement multiple try/catch statements as stated above.

So, What's the Catch?

As stated above, the try/catch/finally blocks are all local scope – so nothing declared in there will transfer outside of those individual blocks. This can certainly cause some major confusion if you do not take this into account.

With local scope, this means that something declared in try will not pass to catch or finally as it is ‘block specific values’. Now, there are ways to get around local scope….but most of them require a more recent version of JS, which we do not have.

The good news is that there is one that can help us get around this. By declaring the variable globally and then setting a value in the local scope, this will retain that value on the variable.

This example shows local scope not passing the value of the variable:

<script runat=server>
try {
    var a = 1;  //local scope only
    throw "Error";
} catch(e) {
    Platform.Response.Write(a); 
    //will write a null value to the page, as 'a' is undefined in this context
}

Platform.Response.Write(a); 
//will write a null value to the page, as 'a' is undefined in this context
</script>

Declaring the variable as a global variable, the value will be passed:

<script runat=server>
var a;

try {
    a = 1;  
    //cannot use 'var' here or it will be declared again,
    // making it local and not retaining the value
    throw "Error";
} catch(e) {
    Platform.Response.Write(a); 
    //will write 1 as a was set to 1 prior to the exception
}

Platform.Response.Write(a); 
//will write 1 as a was set to 1 inside the try/catch block
</script>

As noted in the comments of the sample scripts, the major thing to watch out for is do not ‘re-declare’ your variable in the blocks. This means, avoid using ‘var’ on your variables inside try/catch, only set the value (at least for your global variables). If you re-declare your variable with ‘var’ inside the block, this will make it be considered a local variable and will not retain the value.

But Wait...There's More!

This one comes straight from learnings inside of a Salesforce Stack Exchange Question. In this post, the issue that shd.lux was facing is that by having a Redirect in both the ‘try’ and ‘catch’ blocks would cause the ‘catch’ redirect to always run. This was true regardless if there was any errors inside the try block or not. Huh?! What the heck!? Why???

Well! It appears this is a well documented .Net issue revolving around that Redirects will throw a ‘ThreadAbort’ exception, which would trigger the catch block to run instead. Now SFMC SSJS does not have all the great exception handling capabilities that you can see in languages like .Net, so what the heck do we do?

Approach 1

My recommended approach is to utilize an if statement inside the catch to watch for the 'AMPScriptRedirectException' (as it is called in SFMC error description) to be able to only have a redirect in the catch if it is appropriately needed. With this approach, the redirect will not be inside the catch statement when the RedirectException is thrown. Which means the try statement will correctly run and redirect accordingly.

Below is my recommended code:

<script runat="server">
    var redirect = 'https://google.com'

    try{
          Platform.Response.Redirect(redirect')
    } catch(e) {
        var desc = e.description; //Pulls the description from error object
        if(desc.indexOf("ExactTarget.OMM.AMPScriptRedirectException") > -1) {
          Platform.Response.Write(desc) //This is arbitrary as will not be run
        } else {
          redirect = 'https://yahoo.com'
          Platform.Response.Redirect(redirect)
        }
    }
</script>

The major benefit to this approach (other than that I submitted it as my answer) is that it allows you to differentiate how each type of exception/error is handled via the conditional. It allows you to create additional ‘else if’ statements to potentially have other Redirects or handling/logging options. It also is very readable and clean – allowing for easy maintenance and low processing requirements.

Approach 2

A second approach to this issue is to utilize a second try/catch inside of your original try/catch statement for the redirect. This was provided by the Original Poster (shd.lux) based on the accepted answer by Daniel Koch. Inside this example, there is a nested try/catch inside the original one. Because there is no ‘catch’ action in the nested try/catch, the 'try' redirect will function correctly. But if there are any exceptions in code surrounding this nested try/catch, it will be sent to the Redirect in the parent try/catch.

Code snippet example taken from the answer:

<script runat="server" language="javascript">
Platform.Load("core", "1.1.1");

try {

    // do stuff
    try{
        Redirect("http://www.google.com", false);    
    } catch(e) {}

} catch (e) {
    Redirect("http://www.yahoo.com", false);
}
</script>

I do see the value in this solution, especially in its simplicity, but I do worry about any risk that could come from attempting it. I worry if there is an actual error with the Redirect, your page will not toss any exceptions or anything as it will go to the empty catch - meaning it could potentially not redirect like intended, but instead stay on page.

The solution seems a bit to me like using duct tape to ‘fix’ a broken window. Sure, it will work and keep the water out of the car, but it is not pretty and can limit capabilities – like opening it or seeing through it. Depending on your comfort level and needs, this is a viable approach as long as you consider the approach and ensure it meets your needs and does not limit future requirements.

Approach 3

The final way (at least that I will cover here) is what was recommended by Daniel Koch. His recommended way is to declare your ‘try’ redirect var url above the try/catch as a global variable. Then if the ‘try’ throws an exception/error, you reassign a ‘catch’ value to it inside your ‘catch’ block. This value is then used in your Redirect function that is run below the try/catch block and will take you to the URL that was assigned or originally declared as appropriate.

Sample code taken from the answer:

<script runat="server">
    var redirect = 'http://www.google.com'
  
    try{
        //do your stuff here
    } catch(e) {
        redirect = 'http://www.yahoo.com'
    }
  
    Platform.Response.Redirect(redirect)
</script>

This is a very simple and elegant solution, but is also very specific to this particular issue. It doesn’t offer the same flexibility that I feel the conditional ‘catch’ block offers around handling errors. For instance, the redirect exists wholely outside the try/catch block, meaning if there is an error there, it will cause a 500 error on the page.

Now, that being said, I would definitely recommend this if your use-case is singular and you have no need for detailed exception handling or worries on the Redirect tossing any errors. This approach is easy to read, maintain and debug.

In Conclusion

So, as you can see, there are a few things to keep in mind while using try/catch. The above is certainly not comprehensive and there are other things that can come up and cause ‘hiccups’ when using try/catch, but I feel the above examples should shed light on the majority of the ‘catches’ of try/catch. Hope this helps make debugging less painful for you!

All Rights Reserved
Made with by your fellow SFMC users.
All Rights Reserved
Made with by your fellow SFMC users.