slifty
slifty

Reputation: 13821

How can I safely access caught Error properties in TypeScript?

The Situation

I am using TypeScript, and have a try / catch block. The caught error is typed (Error). I would like to use the error's message attribute.

I am using eslint with the @typescript-eslint/no-unsafe-assignment rule enabled.

The Code

try {
  throw new Error('foo');
} catch (err: Error) {
  const { message }: { message: string } = err;
  return {
    message: `Things exploded (${message})`,
  };
}

The Problem

When I run my linter, I receive the following:

  4:9  error  Unsafe assignment of an any value  @typescript-eslint/no-unsafe-assignment

This is confusing to me, since the error is typed (Error).

The Question

How can I catch an error in TypeScript and access the Error's properties?

Upvotes: 91

Views: 90934

Answers (6)

Will
Will

Reputation: 925

I'd suggest using the ternary operator with a type check. The accepted answer allows execution to continue in the case the error is not an Error (e.g. someone has decided to throw 42 or something equally valid yet unreasonable)

try {
    throw new Error('foo');
} catch (e) {
    const message = e instanceof Error ? e.message : "Unknown error."
    return {
        message
    }
}

Upvotes: 14

Hakan Saglam
Hakan Saglam

Reputation: 131

Visit https://devblogs.microsoft.com/typescript/announcing-typescript-4-4/

" Using unknown in Catch Variables Users running with the --strict flag may see new errors around catch variables being unknown, especially if the existing code assumes only Error values have been caught. This often results in error messages such as:

Property 'message' does not exist on type 'unknown'. Property 'name' does not exist on type 'unknown'. Property 'stack' does not exist on type 'unknown'.

To get around this, you can specifically add runtime checks to ensure that the thrown type matches your expected type. Otherwise, you can just use a type assertion,

add an explicit : any to your catch variable, or turn off --useUnknownInCatchVariables."

you can simply add in tsconfig.json:

"compilerOptions": { "useUnknownInCatchVariables": false }

or you can use like:

catch (e: any) {console.log(e.message)}

Upvotes: 6

Trevor Robinson
Trevor Robinson

Reputation: 16604

TL;DR: I made a tiny library to make catching unknown safely easy:

import { asError } from 'catch-unknown';

try {
  throw new Error('foo');
} catch (err) {
  return {
    message: `Things exploded (${asError(err).message})`,
  };
}

According to Typescript's lib.es5.d.ts file, an Error is any object with string properties called name and message and optionally stack.

interface Error {
    name: string;
    message: string;
    stack?: string;
}

In practice, errors are generally created using the Error constructor (which is required for instanceof Error to be true):

interface ErrorConstructor {
    new(message?: string): Error;
    (message?: string): Error;
    readonly prototype: Error;
}

declare var Error: ErrorConstructor;

The catch-unknown library provides two simple functions:

export declare function isError(err: unknown): err is Error;
export declare function asError(err: unknown): Error;

isError is a typical type guard, which checks for the correct property types, as opposed to using instanceof (for maximum compatibility). asError is essentially isError(err) ? err : { name: typeof err, message: String(err) }, plus handling of a few special cases. 99.99% of the time, values you catch are already conform to Error, so all that happens is a quick type check.

Upvotes: 5

Jamie Pate
Jamie Pate

Reputation: 2062

Since Typescript 3.7 you can make an assertion type guard

export function assertIsError(error: unknown): asserts error is Error {
    // if you have nodejs assert: 
    // assert(error instanceof Error);
    // otherwise
    if (!(error instanceof Error)) {
        throw error
    }
}
} catch (err) {
  assertIsError(err);
  // err is now typed as Error
  return { message: `Things exploded (${err.message})` };
}

Upvotes: 34

Jason Owen
Jason Owen

Reputation: 7660

TypeScript 4.0 introduced the ability to declare the type of a catch clause variable... so long as you type it as unknown:

TypeScript 4.0 now lets you specify the type of catch clause variables as unknown instead. unknown is safer than any because it reminds us that we need to perform some sorts of type-checks before operating on our values.

We don't have the ability to give caught errors arbitrary types; we still need to use type guards to examine the caught value at runtime:

try {
  throw new Error('foo');
} catch (err: unknown) {
  if (err instanceof Error) {
    return {
      message: `Things exploded (${err.message})`,
    };
  }
}

Upvotes: 145

devdgehog
devdgehog

Reputation: 625

The catch parameter type can only be any or unknown. I think in your case it's considered as any.

Is this working in your environment?

try {
  throw new Error('foo');
} catch (err: unknown) {
  const { message } = err as Error; // I removed : { message: string } because it should be infered
  return {
    message: `Things exploded (${message})`,
  };
}

Upvotes: 5

Related Questions