try..finally

You’re probably familiar with how the try..catch block works. But have you ever stopped to consider the finally clause that can be paired with it? In fact, were you aware that try only requires either catch or finally, though both can be present if needed.

The code in the finally clause always runs (no matter what), and it always runs right after the try (and catch if present) finish, before any other code runs. In one sense, you can kind of think of the code in a finally clause as being in a callback function that will always be called regardless of how the rest of the block behaves.

So what happens if there’s a return statement inside a try clause? It obviously will return a value, right? But does the calling code that receives that value run before or after the finally?

  1. function foo() {
  2. try {
  3. return 42;
  4. }
  5. finally {
  6. console.log( "Hello" );
  7. }
  8. console.log( "never runs" );
  9. }
  10. console.log( foo() );
  11. // Hello
  12. // 42

The return 42 runs right away, which sets up the completion value from the foo() call. This action completes the try clause and the finally clause immediately runs next. Only then is the foo() function complete, so that its completion value is returned back for the console.log(..) statement to use.

The exact same behavior is true of a throw inside try:

  1. function foo() {
  2. try {
  3. throw 42;
  4. }
  5. finally {
  6. console.log( "Hello" );
  7. }
  8. console.log( "never runs" );
  9. }
  10. console.log( foo() );
  11. // Hello
  12. // Uncaught Exception: 42

Now, if an exception is thrown (accidentally or intentionally) inside a finally clause, it will override as the primary completion of that function. If a previous return in the try block had set a completion value for the function, that value will be abandoned.

  1. function foo() {
  2. try {
  3. return 42;
  4. }
  5. finally {
  6. throw "Oops!";
  7. }
  8. console.log( "never runs" );
  9. }
  10. console.log( foo() );
  11. // Uncaught Exception: Oops!

It shouldn’t be surprising that other nonlinear control statements like continue and break exhibit similar behavior to return and throw:

  1. for (var i=0; i<10; i++) {
  2. try {
  3. continue;
  4. }
  5. finally {
  6. console.log( i );
  7. }
  8. }
  9. // 0 1 2 3 4 5 6 7 8 9

The console.log(i) statement runs at the end of the loop iteration, which is caused by the continue statement. However, it still runs before the i++ iteration update statement, which is why the values printed are 0..9 instead of 1..10.

Note: ES6 adds a yield statement, in generators (see the Async & Performance title of this series) which in some ways can be seen as an intermediate return statement. However, unlike a return, a yield isn’t complete until the generator is resumed, which means a try { .. yield .. } has not completed. So an attached finally clause will not run right after the yield like it does with return.

A return inside a finally has the special ability to override a previous return from the try or catch clause, but only if return is explicitly called:

  1. function foo() {
  2. try {
  3. return 42;
  4. }
  5. finally {
  6. // no `return ..` here, so no override
  7. }
  8. }
  9. function bar() {
  10. try {
  11. return 42;
  12. }
  13. finally {
  14. // override previous `return 42`
  15. return;
  16. }
  17. }
  18. function baz() {
  19. try {
  20. return 42;
  21. }
  22. finally {
  23. // override previous `return 42`
  24. return "Hello";
  25. }
  26. }
  27. foo(); // 42
  28. bar(); // undefined
  29. baz(); // "Hello"

Normally, the omission of return in a function is the same as return; or even return undefined;, but inside a finally block the omission of return does not act like an overriding return undefined; it just lets the previous return stand.

In fact, we can really up the craziness if we combine finally with labeled break (discussed earlier in the chapter):

  1. function foo() {
  2. bar: {
  3. try {
  4. return 42;
  5. }
  6. finally {
  7. // break out of `bar` labeled block
  8. break bar;
  9. }
  10. }
  11. console.log( "Crazy" );
  12. return "Hello";
  13. }
  14. console.log( foo() );
  15. // Crazy
  16. // Hello

But… don’t do this. Seriously. Using a finally + labeled break to effectively cancel a return is doing your best to create the most confusing code possible. I’d wager no amount of comments will redeem this code.