How to type an error in try-catch block

5 min read

Cover Image for How to type an error in try-catch block
TL;DR: TypeScript catch clause variables can now be typed as unknown for better type safety since version 4.0.
Try-catch blocks are used to handle exceptions. Before TypeScript 4.0, the catch clause variable was typed as any, which meant it could be anything. This could lead to unsafe code. Since TypeScript 4.0, the catch clause variable can be typed as unknown, which is safer than any. Unknown reminds the developer to check the type of the error before using it. It is important to be careful with errors because they can be any type, not just an Error object. Type guards can be used to narrow down the type of the error before using it. This makes the code safer overall.

Do you know how to handle or type an error in TS in try-catch block

But before going into catch block let's first see what is exception and its types

In TypeScript, an exception refers to an unexpected or exceptional situation that occurs during the execution of a program, causing the normal flow of control to be disrupted. When an exception occurs, it typically results in the termination of the current operation or function and triggers a process known as exception handling.

In TypeScript, exceptions can broadly be categorised into two types: user-defined exceptions and built-in exceptions.

  1. User-defined exceptions: These are exceptions that developers define themselves within their TypeScript code. They can create custom error classes or structures to represent specific error scenarios that might occur within their application. For example, if you're building a banking application, you might define a custom exception class called InsufficientFundsError to represent situations where a user tries to withdraw more money than they have in their account.

  2. Built-in exceptions: These are exceptions that are part of the JavaScript or TypeScript language itself, or provided by libraries and frameworks. Some common built-in exceptions include TypeErrorSyntaxErrorRangeErrorReferenceError or NetworkError

These built-in exceptions are part of the JavaScript runtime environment and are thrown automatically under certain conditions.

we use try-catch block to handle exception as:

function logError(error: Error) {
    // LOG ERROR WHERE EVER YOU WANT maybe server
    console.log(error.message);
}

function run() {
    try {
        // YOUR CODE

        // Simulating error by throwing it
        throw new Error("Throwing error")
    } catch (e) {
        // HANDLE EXCEPTION
        logError(e);
    }
}
run();

But what exactly is the type of e in catch clause. Is it Error, any or something else

Before Typescript 4.0

Before Typescript 4.0 catch clause variables were any by default.

It means you can call do anything with that e variable as you can see below. It is like we are writing JS code without any type safety.

function run() {
    try {
        // YOUR CODE
        throw new Error("Throwing error")
    } catch (e) {
        console.log(e.toUpperCase());
        e++;
        e.method()
    }
}
run();

If you execute above code then it will throw error as below if you try to do invalid operations because e is typed as any and you can do anything with it. This code is totally unsafe and can leads to undesirable result.

After Typescript 4.0

But from v4.0 Typescript allow us to specify the type of catch clause variable as unknown.

That means you can specify unknown in place of any.

also, with Typescript 4.0 you have the ability to make your catch clause variable unknown by default with useUnknownInCatchVariables flag.

💡
unknown is safer than any because it reminds us that we need to perform some sorts of type-checks before operating on our values.

Instead of saying any for the type of the error you caught (which means TypeScript won't really check what type of error it is), you can now use unknown.

Using unknown is a bit like saying, "Hey, TypeScript, I'm not sure what type of error I'm dealing with here." It's a reminder to you, the developer, that you should be careful and check what kind of data you're dealing with before you do anything with it.

So, TypeScript is nudging us to be more careful and make sure we're doing the right checks before we use the error data. This helps prevent unexpected problems in our code and makes it safer overall.

So if you try to do any operation with catch clause variable then Typescript will tell you upfront that you have to narrow down your type to perform operation.
Also, there is no guarantee that the error thrown is always subtype of Error. You can also throw exception manually like.

function run() {
    try {
        // YOUR CODE
        throw "Throwing string"
    } catch (e) {
        console.log(typeof e);
    }
}
run();

So problem is you don't know what exactly is type of e here, so all you have to do is to narrow it down using type guards and then perform operation.

So the above code will now be more type safe with unknown because you cannot perform any operation on e without narrowing down its type

function run() {
    try {
        // YOUR CODE
    } catch (e) {
        if (typeof e === "string") {
            console.log(e.toUpperCase());
        }

        if (typeof e === "number") {
            e++;
        }

        if(typeof e === "object" && "method" in e && typeof e.method === "function") {
            e.method();
        }
    }
}
run();

Since you are narrowing type of e and then performing operation on it which will make our code type-safe.

If you want to dive into the change then you view this PR

Hope you learn something new 👍