'How do you use typed errors in async catch()

I am using an async function to call an existing promise-based API which rejects the promise with a typed error.

You could mock this behavior like this:

interface ApiError {
  code: number;
  error: string;
}

function api(): Promise<any> {
  return new Promise((resolve, reject) => {
    reject({ code: 123, error: "Error!" });
  });
}

Now with promises, I can annotate the error type to ApiError:

api().catch((error: ApiError) => console.log(error.code, error.message))

But when using async if I try to annotate the error type in try ... catch():

async function test() {
  try {
    return await api();
  } catch (error: ApiError) {
    console.log("error", error);
  }
}

It compiles with error:

Catch clause variable cannot have a type annotation.

How, then, do I know what kind of error I'm expecting? Do I need to write an assertion in the catch() block? Is that a bug/incomplete feature of async?



Solution 1:[1]

You cannot assign a custom type to your error directly but you can use a type guard. A type guard is a function which helps TypeScript's compiler to figure out the type of your error.

Writing a type guard with a type predicate is very easy. You just need to implement a boolean check which accepts any value and figures out if this value has a property of your custom type.

Given your example, I can see that your ApiError has a custom property called code, so a type guard could look like this:

function isApiError(x: any): x is ApiError {
  return typeof x.code === 'number';
}

Note the x is ApiError return type. That's a type predicate!

Using the isApiError function from above, it will be possible now for TypeScript to infer your custom error type:

interface ApiError {
  code: number;
  error: string;
}

async function test() {
  const isApiError = (x: any): x is ApiError => {
    return typeof x.code === 'number';
  };

  try {
    return await api();
  } catch (error) {
    if (isApiError(error)) {
      // Thanks to the type guard, TypeScript knows know what "error" is
      console.log(error.code);
    }
  }
}

You can see a type guard in action here: https://www.youtube.com/watch?v=0GLYiJUBz6k

Solution 2:[2]

This error has nothing to do with async. You can't have typed catch variables.

The reason for this is simple: the types in TypeScript only exist until the code is compiled. Once it's compiled, all you've got is typeless JavaScript.

Having type filters on catch clauses would require checking the errors' types at runtime, and there's simply no reliable way to do that, so I would say such a feature is unlikely to ever be supported.

Solution 3:[3]

A simple solution

You just need to cast the error type with type assertion.

catch (err) {
  const error = err as CustomErrorType;
  ...
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Benny Neugebauer
Solution 2 JLRishe
Solution 3 Masih Jahangiri