I Finally Understand The Finally Part Of A Try/Catch Control Statement
I love a good Try / Catch block! In fact, I used one just the other day when dealing with transactional rollbacks involving 3rd party APIs. But, as far as the Try / Catch control flow goes, I never quite understood the need for a "Finally" statement; if you have a Catch statement, why not just move the contents of the "Finally" statement to after the body of the Try / Catch block? People have tried to explain this to me many times, and I'm sure I've nodded and said, "Oh, that makes sense." But, it never really did made sense - until, that is, I read Eloquent Javascript by Marijn Haverbeke.
My moment of clarity came when Haverbeke demonstrated a Try / Catch block that didn't have a Catch statement; it only had Try and Finally. Honestly, I didn't even know such a thing was technically possible! But seeing this suddenly made so much sense - if you don't have a Catch statement, then of course you can't just move the contents of the Finally statement to after the Try / Catch block! A Finally statement would be the only way to execute localized clean-up.
To explore the Try / Catch / Finally trinity a bit more, I set up a few demos. The first uses all three statements - this is the kind of scenario in which the Finally statement never made any sense to me.
<!DOCTYPE html>
<html>
<head>
<title>Understanding Try/Catch And Finally</title>
<script type="text/javascript">
// Execute a try/catch block with a finally.
try {
// Throw an error so the catch is triggered.
throw( new Error( "Blam!!!!" ) );
} catch( error ){
// Catch the error.
console.log( "Caught your error:", error.message );
} finally {
// Execute the clean-up code.
console.log( "Finally!" );
}
// We got past our try/catch.
console.log( "Back to safety." );
</script>
</head>
<body>
<!-- Left intentionally blank. -->
</body>
</html>
Here, the Try statement throws an error, Catch handles it, and the Finally executes clean-up. When we run the above code, we get the following console output:
Caught your error: Blam!!!!
Finally!
Back to safety.
As you can see, the Catch statement handled the error and the Finally statement executed afterward. While it is not demonstrated by this specific control flow, the Finally statement would have executed regardless of whether or not an exception was thrown.
At this point, I would look at the code and wonder what role the Finally statement was actually playing? As far as I have ever been concerned in the past, the Finally code could simply be factored-out with the exact same end-result.
The Finally statement starts to make more sense, however, when you remove the Catch statement from the workflow:
<!DOCTYPE html>
<html>
<head>
<title>Understanding Try/Catch And Finally</title>
<script type="text/javascript">
// Execute a TRY-ONLY block with a finally.
try {
// Throw an error so the catch is triggered.
throw( new Error( "Blam!!!!" ) );
} finally {
// Execute the clean-up code.
console.log( "Finally!" );
}
// We got past our try/catch.
console.log( "Back to safety." );
</script>
</head>
<body>
<!-- Left intentionally blank. -->
</body>
</html>
Here, the Try statement raises an unhandled exception; the Finally statement then becomes the only way to execute any kind of local clean-up before the exception bubbles up through the call stack. When we run this code, we get the following console output:
Finally!
[Break On This Error] throw( new Error( "Blam!!!!" ) );
As you can see, the Finally statement executed before the error was handled by the environmental container.
I am not sure why I would have a Try statement without a Catch statement; but, that seems to be the only structure in which my brain will let me justify the Finally statement.
So far, we've been looking at a single call context; when we add an additional function call to the stack, however, it gets even more interesting / confusing. In this last demo, we'll use both a Catch and a Finally statement; and, just to make this extra special, both of these statements will contain their own Return statement:
<!DOCTYPE html>
<html>
<head>
<title>Understanding Try/Catch And Finally</title>
<script type="text/javascript">
// I contain a try/catch/finally block to see how return
// statements interact with the function error handling.
function test(){
try {
// Throw an error so the catch is triggered.
throw( new Error( "Blam!!!!" ) );
} catch( error ){
// Log our location.
console.log( "... catch" );
// Return out of our try/catch.
return( "In Catch" );
} finally {
// Log our location.
console.log( "... finally" );
// Return out of our finally.
return( "In Finally" );
}
}
// Log the results of our test() method.
console.log( test() );
</script>
</head>
<body>
<!-- Left intentionally blank. -->
</body>
</html>
Unless you know 100% what the Finally statement does, this has to be confusing code! When we run it, we get the following console output:
... catch
... finally
In Finally
So, the Catch statement runs, executes its Return statement, which hands control over to the Finally statement, which runs and then executes its Return statement (which is the value that gets passed back to the call stack). But, what if the Finally statement didn't have its own Return statement? Well, in that case (not shown in the code), the Finally statement would run and then the return value of the Catch statement would get passed back to the call stack - isn't that obvious?!? (he says jokingly knowing that it's hard to write code more confusing than this).
After reading Eloquent Javascript by Marijn Haverbeke, my understanding of the Finally statement within a Try / Catch block is much more clear. But, to be honest, I am still having a little trouble seeing a great use-case for it. I suppose that to some extent, it can be used in place of a nested Try / Catch block; or, a Catch statement that uses a throw or rethrow approach.
Want to use code from this post? Check out the license.
Reader Comments
A try/finally construct has the advantage of not messing with debugging info: you keep the original exception intact.
So if an exception is thrown into your try block, all the debugging information (function calls, file, line number, ...) will be the same as if there was no try block at all. This is a huge advantage when debugging in order to determine where the exception was initially thrown, especially when you have lots of function calls inside your try block.
On the other hand, if you add a catch statement and rethrow the exception, you lose the information: the exception you re-throw in the catch block will now refer to the call stack, file and line number of the catch block. So any exception getting out of a try/catch-rethrow/finally construct will seem to be originating from the catch block.
Not also that a try/finally construct won't work in IE7 where you are forced to add a catch/rethrow or else the finally block will never get executed if an exception is thrown in the try block.
We use a try/finally in Deferreds in jQuery when executing the internal callback list. At one point, we tried to fix the issue in IE7 by adding a catch/re-throw (which made it in 1.5.1) but quickly backed it out for 1.5.2 seeing as it made debugging a nightmare (it made it impossible to determine which of the many possible callbacks had actually throw the exception in the first place).
So the Deferred is deadlocked in IE7 if a callback throws an exception but that's something we decided to be less of a pain than making debugging a nightmare in all browsers.
@Julian,
I have definitely never factored debugging into the picture. It never even occurred to me that rethrow-style control flow actually changes the exception information. I wonder if that is true for all programming languages? I *feel*, but could be wrong, that ColdFusion passes through the original error with a <CFReThrow/> statement. I could be way off-base, however (debugging beyond "bugs" has never been something I put much through into).
Thanks for the insight - that definitely sheds a new light on the matter. And, good to know about IE7 as well.
+1 on Julian's comments. A try/finally without a catch is definitely going to break in IE less then 8. In fact, I had to file a bug against jQuery 1.5 because of that (it's fixed in 1.5.1).
@Todd,
Glad that got fixed!
Hi Ben, as far as I can remember, if you do a rethrow in ColdFusion, you lose the original stacktrace and tag context.
@Ben,
This is pretty unique to JavaScript as far as I know. It doesn't really make sense when you think about it but is damn consistent across browsers.
The only explanation I have is that, in JavaScript, you can pretty much throw anything. So if you take the example of throwing a number (like throw 666), it comes to reason that the actual debugging info cannot be tagged onto the number thrown itself. I suppose browsers actually store the debugging info globally in an internal structure at each throw. It could explain why they get it wrong in case of a re-throw.
Since most languages require a specific type/class for thrown values (Throwable interface in Java, Exception class in PHP, etc), these environments can safely transport debugging info within the thrown value itself and re-throws become transparent.
You could argue that, in these languages, some code could catch an exception, move it around and re-throw it much later on in another part of the application, effectively hiding a lot of info from the coder. However I see this as a feature rather than a problem and would much rather prefer this to how JavaScript does things.
I never used ColdFusion so I cannot say what actually happens when the CFReThrow tag is used but if thrown values are constraint to a given type/class in ColdFusion, then it's safe to assume they do transport debugging info.
@Ben, @Julian
I just did a quick check: it turns out that cfrethrow DOES preserve the original call stack info, so Julian was right and I remembered it wrong.
(I checked it in Adobe CF8)
The finally is actually very useful when you have mutiple catches in a single try/catch block, but you want to, let's say, close an FTP connection if any error occur. Instead of adding the ftp closing code in every single catch block, you can just put in the finally block.
In other languages such as Java it makes more sense, since there are more "features" that you will probably need to close to free some memory space.
@Henrique,
Thats the explanation that makes using finally click for me. Thanks.
@all would a finally be.good for a rollback statement?
Maybe if you try this and that but the xth step fails you could reset your states back to what they were prior to the try?
@Martijn, @Julian,
Ah, good to know. To be honest, ColdFusion is really the only language where I've done a good amount of manual raising of exceptions. In Javascript, I don't do a whole lot of Try/Catch; I mostly used it in this example because the Eloquent Javascript book is in, well, Javascript :)
I wonder if you can use a Try without a Catch in ColdFusion; I guess you can. I think the CFFinally tag may have only been introduced in ColdFusion 9 (the latest release).
@Henrique,
Yeah, good thought. But, also, this only makes sense if the CFCatch's are going to halt the processing of the given call stack (otherwise, you could really just factor the Finally out of the try/catch). That's why the lack of a catch (in the Eloquent Javascript book) was what finally made things click, mentally.
@Randall,
You wouldn't want to put a Rollback in the Finally statement because the Finally statement executes if there IS or IS NOT an exception raised. So, you'd probably end up rolling back all transactions, not just the ones that precipitated errors.... and least I think.
@Ben, You are correct, at least according to Wiki's article about exception handling (C++).
But that doesn't make sense to me. Why have a Finally if it's going to execute regardless? Wouldn't you just put it outside of the Try?
@Ben, sorry, I re-read the entire post and noticed you pose the same question.
@Henrique Feijo,
Re-reading your response makes much more sense too. I was looking at some Java and noticed
reader.close()
writer.close()
That would be a good case for CF too -- tying up the common loose ends.
@Randall,
No problem my man. At least if we both had the thought, it probably means that we're not crazy ;)
I was going to say basically what @Henrique Feijo said. I use the finally in server-side code for closing database connections and streams. You wouldn't want to leave a connection open if a command happens to fail and sometimes you don't have a need to handle the exception at that point so just having the finally statement is enough.
Hi Ben,
There are other reasons why you may want to use a cffinally clause where you cannot replace it with code outside of the cftry clause. Mostly in elaborate logging schemes with nested cftry where errors bubble up so to speak...
Bear with me for a moment as this may not seem very useful at first...
Does that make sense?
For that matter, it makes a lot more sense if you are for instance using aop... Where you for instance have a general errorLogger method that wraps around your annotated functions.
Ok sorry, I don't mean to carry on like this, but I felt compelled to show an AOP-ish example:
In this case you have NO option but to use a finally clause...
I hope this helps :)
The underlying reason for the finally block is to clean up mess you might have left behind, like an open FTP connection, or a Java object that would otherwise leak, or an SQL transaction that needs to be closed or rolled back.
But it's not just when errors are thrown.
The finally block does (or should) execute even if you <cfabort>, <cfreturn>, or <cfexit>. And yes, if you <cfthrow> something that gets caught elsewhere.
No matter what happens, you should be able to rely upon your finally block running. That cannot be said for code sitting underneath </try>.
Ben,
I can't speak to how it works in js, but in ColdFusion and I believe ruby the finally block is used when you have code that must run regardless of if an error occurs in the try block or in the catch block.
For reference this article I wrote back in january http://www.neiland.net/blog/article/where-you-should-use-cffinally/
I can't believe I didnt notice the datestamp on this article. My bad lol
@Steven,
No worries - just read your post. Good stuff! This is definitely comforting and good timing - I was just doing a sanity-check this morning.