Skip to main content
The try...finally statement is a sequential control flow construct that guarantees the execution of a specific block of code (the finally clause) immediately after the execution of a try block. This execution is guaranteed regardless of whether the try block completes normally, throws an exception, or exits prematurely via a control transfer statement such as return, break, or continue. In TypeScript, a try block must be followed by either a catch block, a finally block, or both. Omitting the catch block is syntactically valid when finally is present.

Execution Mechanics

The control flow between try and finally adheres to strict evaluation rules based on how the try block terminates. try...finally fully supports asynchronous code; if an await is used inside the finally block, it will suspend execution asynchronously before proceeding.
  1. Normal Completion: If the try block executes to completion without encountering an exception or control transfer, the finally block executes sequentially afterward. Control then passes to the next statement in the outer scope.
  2. Exception Propagation: If an exception is thrown within the try block, execution of the try block halts. The finally block executes sequentially. Once the finally block completes (and any asynchronous operations within it resolve), the original exception continues to propagate up the call stack.
  3. Control Transfer (return, break, continue): If the try block encounters a control transfer statement, the expression associated with that statement (e.g., the value to be returned) is evaluated first. Execution is then suspended, the finally block executes, and only upon its completion does the control transfer take effect.

Return Value and Exception Overriding

The finally block possesses the ability to intercept and override the termination state of the try block. This introduces specific behaviors regarding return values and exceptions: 1. Overriding a Return Value If both the try block and the finally block contain return statements, the return statement within the finally block dictates the final resolved value of the function. The return value from the try block is discarded at runtime.
2. Suppressing an Exception If the try block throws an exception, but the finally block executes a return statement, the exception is swallowed (suppressed). The function will exit normally, returning the value specified in the finally block, and the caller will not receive the exception.
3. Overriding an Exception If the try block throws an exception, and the finally block also throws an exception, the exception generated by the finally block takes precedence. The original exception from the try block is lost, and the finally exception propagates to the caller.

Type System Implications

TypeScript’s compiler handles try...finally blocks with specific type inference rules that do not always mirror runtime execution overriding. Return Type Aggregation TypeScript aggregates all return statements for return type inference without eliminating dead code from overridden returns. If a try block returns one type and the finally block overrides it by returning another, TypeScript infers the return type as a union of both, even though the try block’s return is impossible to reach at runtime.
Exception Type Inference Thrown exceptions evaluate to the never type and naturally do not affect the return type union. If a try block throws and the finally block returns a value, TypeScript correctly infers the return type solely from the finally block.
Explicit never Signatures If a function is explicitly typed with a never return signature, returning any value inside the finally block will cause a standard type checking error. This occurs simply because returning a value violates the explicit never signature, regardless of the try block’s behavior.
Tired of Poor TypeScript Skills? Fix That With Deep Grasping!Learn More