Как я могу безопасно получить доступ к свойствам обнаруженной ошибки в TypeScript?

#typescript

#typescript

Вопрос:

Ситуация

Я использую TypeScript, и у меня есть блок try / catch. Обнаруженная ошибка вводится ( Error ). Я хотел бы использовать атрибут ошибки message .

Я использую eslint с @typescript-eslint/no-unsafe-assignment включенным правилом.

Код

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

Проблема

Когда я запускаю свой линтер, я получаю следующее:

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

Это сбивает меня с толку, поскольку ошибка вводится ( Error ) .

Вопрос

Как я могу перехватить ошибку в TypeScript и получить доступ к свойствам ошибки?

Ответ №1:

В TypeScript 4.0 появилась возможность объявлять тип catch переменной предложения… пока вы вводите его как unknown :

TypeScript 4.0 теперь позволяет указывать тип catch переменных предложения как unknown вместо. unknown безопаснее, чем any потому, что это напоминает нам, что нам нужно выполнить некоторые проверки типов, прежде чем работать с нашими значениями.

У нас нет возможности присваивать перехваченным ошибкам произвольные типы; нам все равно нужно использовать средства защиты типов для проверки перехваченного значения во время выполнения:

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

Комментарии:

1. В дополнение к этому, вполне допустимо (хотя и совершенно необоснованно) использовать значения произвольных типов в Javascript, a-la throw 42 , следовательно, недопустимо не помечать err как Error .

2. @etheryte хотя можно использовать значение любого типа, почему typescript не позволяет автору явно пометить ошибку как определенный тип (при условии, что автор лучше знает код и будет знать тип выдаваемой ошибки)

3. Если бы автор явно знал об ошибке, вызванной его кодом, он бы вообще не использовал types и typescript 🙂

4. в качестве альтернативы ` catch (ex) { if (!(ex instanceof Error)) { throw ex; } … }` который обеспечивает защиту того же типа, но имеет то преимущество, что он не проглатывает любые искаженные ошибки!

Ответ №2:

Начиная с Typescript 3.7, вы можете создать защиту типа утверждения

 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})` };
}
  

Комментарии:

1. Это правильный ответ. Неожиданные типы исключений cought должны быть повторно отброшены (пузырьковое исключение)

Ответ №3:

Я бы предложил использовать тернарный оператор с проверкой типа. Принятый ответ позволяет продолжить выполнение в случае, если ошибка не является ошибкой (например, кто-то решил выбросить 42 или что-то в равной степени допустимое, но необоснованное)

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

Ответ №4:

Посетите https://devblogs.microsoft.com/typescript/announcing-typescript-4-4 /

» Использование unknown в переменных Catch Пользователи, работающие с флагом —strict, могут видеть новые ошибки вокруг неизвестных переменных catch, особенно если существующий код предполагает, что были обнаружены только значения ошибок. Это часто приводит к появлению сообщений об ошибках, таких как:

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

Чтобы обойти это, вы можете специально добавить проверки во время выполнения, чтобы убедиться, что брошенный тип соответствует вашему ожидаемому типу. В противном случае вы можете просто использовать утверждение типа,

добавьте явное: any к вашей переменной catch или отключите —useUnknownInCatchVariables . «

вы можете просто добавить в tsconfig.json:

"compilerOptions": { "useUnknownInCatchVariables": false }

или вы можете использовать как:

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

Ответ №5:

Тип параметра catch может быть только любым или неизвестным. Я думаю, что в вашем случае это считается любым.

Работает ли это в вашей среде?

 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})`,
  };
}
  

Комментарии:

1. Это работает, но я обнаружил, что утверждения типа являются рискованными, поскольку они нарушаются трудными для отладки способами. Что произойдет, если err не является Error ? Лучше выполнить проверку во время выполнения.

2. @JasonOwen Я согласен, я просто не думал об этом. Это очень хороший комментарий, поскольку такой код обычно размещается в точках входа приложения.

Ответ №6:

TL; DR: я создал крошечную библиотеку, чтобы упростить unknown безопасное обнаружение:

 import { asError } from 'catch-unknown';

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

Согласно lib.es5.d.ts файлу Typescript, an Error — это любой объект со строковыми свойствами, вызываемыми name и message и необязательно stack .

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

На практике ошибки обычно создаются с использованием Error конструктора (который необходим для instanceof Error того, чтобы быть истинным):

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

declare var Error: ErrorConstructor;
  

catch-unknown Библиотека предоставляет две простые функции:

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

isError это типичная защита типов, которая проверяет правильные типы свойств, в отличие от использования instanceof (для максимальной совместимости). asError по сути isError(err) ? err : { name: typeof err, message: String(err) } , плюс обработка нескольких особых случаев. В 99,99% случаев значения, которым вы catch уже соответствуете Error , так что все, что происходит, — это быстрая проверка типа.

Комментарии:

1. Это выглядит как отличная библиотека, но репозиторий исчез из GitHub

2. @RobinDaugherty Я только что исправил видимость репозитория на GitHub. Теперь вы должны иметь к нему доступ.