Fun Friends for Functions, Part 2: Handling Exceptions
Previously we looked at how you can bind a function to a set of arguments and then call the resultant function at some time in the future. This technique is very powerful, but as-is it is a ticking bomb. As Amy points out in her blog post on exception handling, you always need to be prepared to handle an exception in your code. Failure to catch an exception usually leads to playback stopping and an error diagnostic from your player.
The trouble is that when you start using bound functions, you don't always have an easy way to deal with exceptions. Typically, a bound function is used as part of an asynchronous callback, and in those cases you don't have any functions further up the stack to save you from unhandled errors. Simple, you might think, I'll just wrap the contents of my function in a try-catch and be done with it.
Not so fast.
Let's look at the example function we were using in the previous blog post:
// function to set a backgroundImage
function SetBackgroundImage(markupId, url)
{
document[markupId].style.backgroundImage = url;
}
This could throw for several reasons – for example, an element with the markupId doesn't exist, or url isn't a correctly-formatted property value. Simply wrapping this up in a try-catch would result in something like this:
// function to set a backgroundImage
function SetBackgroundImage(markupId, url)
{
try
{
document[markupId].style.backgroundImage = url;
}
catch(ex)
{
// What do we do here?!?
}
}
But there's a problem – what do we do when we catch the exception? I don't know. And that's a key thing about exception handling:
Functions should only handle exceptions if they are actually prepared to deal with them. If a function doesn't know what to do in an error condition, it should let its caller figure it out. The exception [groan] to this rule is that your top-level code should always swallow exceptions (unless of course things are so out of whack that you think the player should stop immediately).
In this case, the SetBackgroundImage function has no idea what a failure means. It might be perfectly OK if the background image can't be set correctly, or it could be totally disastrous. It all depends on the context, so the SetBackgroundImage function should remain silent on the topic of exceptions and let the calling function figure out what (if anything) to do with them.
In our particular case, let's say that the exception is benign; we are simply updating some eye-candy graphics and it doesn't matter if they fail to appear. So then: maybe we should write a wrapper function, SafeSetBackgroundImage, that does this for us?
// function to set a backgroundImage, ignoring exceptions
function SafeSetBackgroundImage(markupId, url)
{
try
{
SetBackgroundImage(markupId, url);
}
catch(ex) { }
}
I actually like to use this approach if I am going to be calling the 'Safe' version in a lot of different places, but if it is only in one or two places that seems like too much overhead to someone lazy like me. Anytime you update the parameter list of the wrapped function, you have to update the wrappers, too. It can also hide the intent of your code by sprinkling endless wrapper functions all over the place, and this also has the productivity-reducing effect of polluting IntelliSense with unnecessary function names. Instead, we can use the same kind of nested-function trick we used in the argument-binding case to get a "safe" version of any function:
// return a version of a function that is wrapped in try-catch
// you can optionally provide an errorFunc, which will be
// called with the 'tag' parameter in case of an exception
function Function_WrapInTryCatch(errorFunc, tag)
{
// stash the target function
var func = this;
// return the nested function
return wrappedFunction;
function wrappedFunction()
{
// wrap the original function in a try-catch
try
{
func();
}
catch (ex)
{
// call the errorFunc if it was provided
// note this MUST be resilient to errors; if it throws
// then it is game over.
if (typeof errorFunc == "function")
{
errorFunc(ex, tag);
}
}
}
}
Function.prototype.WrapInTryCatch = Function_WrapInTryCatch;
In this example, we also allow the caller of the WrapInTryCatch function to provide an optional error-handling function that will be called if an error occurs. This allows you to do things like channel all your async exceptions to the same error handling function, which can figure out what to do based on the value of the tag parameter. Now we can re-write the last couple of lines of the example as:
// Call the download function, passing in the set-image function as the callback handler
var setBackgroundHandler = SetBackgroundImage.BindToArguments("button1", "file:///filecache/bar.png");
var wrappedHandler = setBackgroundHandler.WrapInTryCatch();
DownloadFile("https://foo/bar.png", "file:///filecache/bar.png", wrappedHandler);
This helper has the same limitations as the BindToArguments function (specifically: you can't pass any arguments to the returned function, and any tracing information is lost if 'errorFunc' is called) but this is also a solvable problem.