Why Catch clause variable type annotation must be any?

Viewed 23572

I'm using yup to validate a form, and I encountered this type error while trying to make this catch work:

Catch clause variable type annotation must be any or unknown if specified 1196

My Code:

const handleSubmit = async (): Promise<void> => {
    try {
      const isValid = await userSchema.validate(values, { abortEarly: false });
      console.log(isValid);
    } catch (err: ValidationError) {
      console.log(err);
      const errors = getValidationErrors(err);
    }

getValidationErrors function:

export function getValidationErrors(err: yup.ValidationError): Errors {
  console.error(err);
  const validationErrors: Errors = {};
  err.inner.forEach((error) => {
    if (error.path) validationErrors[error.path] = error.message;
  });
  return validationErrors;
}

While searching about it, I found out that Typescript doesn't accept types for clause catch arguments... Why is that? This is just so commom in Java or other languages... I mean like... my solution here is attribute err: any... But isn't type any just something to never be used?

5 Answers

Typescript doesn't let you do that because there's no way that typescript can verify at compile time that the only thing the code can throw is a ValidationError. For that matter, you don't know that either: what if you get a RangeError, because the maximum call stack has been exceeded? Obviously that's unlikely (you'd have to build up a big call stack before calling this), but nothing about this code can guarantee that it won't happen.

If you want the error to have a certain type, you will need to either add code to verify that it is actually that type:

catch (err: unknown) {
  if (err instanceof ValidationError) {
     // Inside this block, err is known to be a ValidationError
  }
}

Or you will need to use type assertions to tell typescript "i know it's this type, trust me":

catch (err: unknown) {
  getValidationErrors(err as ValidationError);
}

This is just so commom in Java or other languages...

Different languages have different constraints on their designs. For example, in Java you can define which errors your function will throw as part of the function definition, as in

public static FileInputStream example(String fileName) throws FileNotFoundException {

Neither javascript nor typescript have a way to specify this in a function definition.

And java lets you have multiple catch blocks, with different types of errors being handled by different ones, but since typescript only exists at compile time, that's not an option. Everything must be handled in a single catch block, and distinguishing which type of error you're dealing with must be done with explicit code.

As others have noted TS doesn't let you use types other than "any" or "unknown" within a catch clause.

You can however cast the error variable a specific type. using myVariable.

try {
  // where some ValidationError object could be thrown
} catch (err: unknown) {
   const errors = getValidationErrors(<ValidationError>err);
}

You cannot write a specific annotation for the catch clause variable in Typescript, because in Javascript a catch clause will catch any exception that is thrown, not just exceptions of a specified type.

Java lets you (actually, requires you) to write the type of an exception because at compile-time it is able to check (for checked exception types) whether that exception can be thrown, but more importantly, at runtime it will check what type of exception was thrown, and only catch it (executing the catch block) if the exception type matches the catch clause.

In Typescript if you want to catch just a specific type of exception, you have to catch whatever is thrown, check if it is the type of exception you want to handle, and if not, throw it again:

try {
    // code that could throw ValidationError
} catch(e: unknown) {
    if(!(e instanceof ValidationError)) { throw e; }
    // code to handle ValidationError
}

In this case, after the if statement, e's type will be narrowed to ValidationError and you can access e.message.

According to this piece of Typescript docs, before version 4.0 of TS, catch clause parameters were usually typed as any, and it could cause a new error when handling the first one.

Example:

try {
  // Do something
} catch (x) {
  console.log(x.message);
}

What if there is no message property in x ? Yes, now you have a new error.

unknown type exists as a safe alternative to any, because operations with unknown type are illegal, so you are forced to type check your unknown variable before doing anything.

Also according to the docs, the type any:

lacks any type-safety which could have errored on invalid operations.

So we can conclude that you should keep avoiding the use of any.

I hope my answer helps you.

You cannot write a specific annotation for the catch clause variable in typescript, this is because in javascript a catch clause will catch any exception that is thrown, not just exceptions of a specified type.

In typescript, if you want to catch just a specific type of exception, you have to catch whatever is thrown, check if it is the type of exception you want to handle, and if not, throw it again.

meaning: check if the error that is thrown is an axios error first, before doing anything.

try {
// do something
}catch (err) {
     // check if error is an axios error
     if (axios.isAxiosError(err)) {
                
      // console.log(err.response?.data)
      if (!err?.response) {
          console.log("No Server Response");
       } else if (err.response?.status === 400) {
         console.log("Missing Username or Password");
       } else if (err.response?.status === 401) {
         console.log("Unauthorized");
        } else {
        console.log("Login Failed");
      } 
   } 
}
Related