【问题标题】:Type-safe error handling without 3rd party libraries没有 3rd 方库的类型安全错误处理
【发布时间】:2018-10-24 14:59:38
【问题描述】:

我知道类型安全错误处理不是 ts 的原因是因为我们缺少函数中的 throws 子句。因此,每当我这样做时:

function mighThrow(input: number): void {
  if (input === 1) {
    throw new TypeError('cannot be one')
  } else if(input === 2) {
    throw new SyntaxError('invalid syntax: 2')
  }
  console.log('all good', input)
}

我无法准确输入错误:

try {
  mighThrow(1)
} catch(e) {
  // e is any, even though it could be TypeError | SyntaxError
}

问题与Promises 相同,捕获函数参数是硬编码的any

interface Promise<T> {
  then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
  catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}

catch 的参数是 any。

所以我的问题是我该如何解决这个问题?如果我是库作者,是否有一种类型安全的方法可以帮助用户弄清楚他们到底在捕捉什么?我知道诸如添加Either 类型并每天调用它之类的解决方案,但这会强制当场处理错误,这只会将代码变成类似于语言会检查异常的东西。

【问题讨论】:

标签: typescript error-handling try-catch


【解决方案1】:

由于目前有no throws in TypeScript,因此任何建议看起来都像是一种解决方法。您正在寻找一种解决方案,它不会为想要忽略错误的用户带来任何开销,但它允许想要捕获错误的用户获得更强的错误类型。

这样的事情怎么样(需要 TS3.1 及更高版本才能工作):

function mighThrow(input: number): void {
  if (input === 1) {
    throw new TypeError('cannot be one')
  } else if (input === 2) {
    throw new SyntaxError('invalid syntax: 2')
  }
  console.log('all good', input);
}
mighThrow.throws = undefined! as TypeError | SyntaxError;

这里我们使用function property declaration 来声明一个名为throws 的幻像属性。幻像属性在运行时并不真正存在,但编译器认为它确实存在,因此编译器会维护额外的类型信息。在这种情况下,undefined! as TypeError | SyntaxError 在运行时只是undefined,但编译器认为mighThrow 有一个throws 类型为TypeError | SyntaxError 的属性。

现在当人们想要捕捉错误时,他们可以使用辅助函数:

type ThrowsType<T> = T extends { throws: infer E } ? E : unknown;
const asTypedError = <FS extends Function[]>(e: any, ...f: FS): ThrowsType<FS[number]> => e;

像这样:

try {
  const x = mighThrow(123);
} catch (err) {
  const typedErr = asTypedError(mighThrow, err);
  // typedErr is now TypeError | SyntaxError
  if (typedErr instanceof TypeError) {
    typedErr; // TypeError
  } else {
    typedErr; // SyntaxError
  }       
}

无论如何它都不完美。它需要错误捕获器来跟踪哪些函数是自己调用的。包含多个函数的 try-catch 块需要将所有这些函数传递给 asTypedError()

declare const alsoMightThrow: { throws: URIError } & ((input: string) => number);
try {
  const x = mighThrow(alsoMightThrow("hey"));
} catch (err) {     
  const typedErr = asTypedError(err, mighThrow, alsoMightThrow); // ?
  // typedErr: TypeError | SyntaxError | URIError
}

实际上并没有保证捕获的错误属于throws 属性中声明的类型...这取决于库维护者来解决这个问题(这很难,因为很多事情都可以在运行时抛出TypeError)。相反,大多数函数不会有这样的throws 属性,因此普通开发人员没有太多动力使用这种类型的错误捕获。如果有人在那里放了一个非throws 函数,asTypedError 将输出一个unknown 错误,这几乎没有any 有用。你必须教育图书馆用户如何使用这个东西,此时只需document 就可以更容易地摆脱你的功能,错误捕捉器可以自己进行instanceof 检查以缩小any 的范围。这是缺少语言功能的解决方法,看起来很像。 ?‍♀️


哦,好吧,希望对您有所帮助。祝你好运!

【讨论】:

  • 我喜欢问这些问题,因为我每次都学到一些关于 ts 的新东西(不知道你可以声明函数 props)!这个想法看起来是个不错的解决方法!
猜你喜欢
  • 2012-07-09
  • 2021-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-26
  • 1970-01-01
相关资源
最近更新 更多