用TypeScript获取一个catch块的错误信息(附代码)

209 阅读3分钟

好吧,让我们来谈谈这个

const reportError = ({message}) => {
  // send the error to our logging service...
}

try {
  throw new Error('Oh no!')
} catch (error) {
  // we'll proceed, but let's report it
  reportError({message: error.message})
}

到目前为止不错吧?嗯,那是因为这是JavaScript。让我们把TypeScript放在这里

const reportError = ({message}: {message: string}) => {
  // send the error to our logging service...
}

try {
  throw new Error('Oh no!')
} catch (error) {
  // we'll proceed, but let's report it
  reportError({message: error.message})
}

那个reportError 的调用并不令人满意。具体来说,就是error.message 。这是因为(从最近开始)TypeScript将我们的error 类型默认为unknown 。 这确实是它的真实情况。在错误的世界里,对于被抛出的错误类型,你不能提供太多的保证。事实上,这和你不能用承诺泛型(Promise)来提供拒绝承诺的.catch(error => {}) 的类型是一样的原因。事实上,它甚至可能根本就不是一个被抛出的错误。它几乎可以是任何东西:

throw 'What the!?'
throw 7
throw {wut: 'is this'}
throw null
throw new Promise(() => {})
throw undefined

说真的,你可以抛出任何类型的东西。所以这很容易,对吗?我们可以为错误添加一个类型注解,说明这段代码只会抛出一个错误,对吗?

try {
  throw new Error('Oh no!')
} catch (error: Error) {
  // we'll proceed, but let's report it
  reportError({message: error.message})
}

没有那么快!我们可以为错误添加一个类型注解,说这段代码只会抛出一个错误。有了这个,你会得到下面的TypeScript编译错误:

Catch clause variable type annotation must be 'any' or 'unknown' if specified. ts(1196)

这是因为尽管在我们的代码中,看起来不可能有其他东西被抛出,但JavaScript有点滑稽,所以第三方库完全有可能做一些奇怪的事情,比如对错误构造函数进行猴子式的修补,抛出一些不同的东西:

Error = function () {
  throw 'Flowers'
} as any

那么开发者该怎么做呢?尽力而为吧!那么,这样如何呢?

try {
  throw new Error('Oh no!')
} catch (error) {
  let message = 'Unknown Error'
  if (error instanceof Error) message = error.message
  // we'll proceed, but let's report it
  reportError({message})
}

我们走吧!现在TypeScript没有对我们大喊大叫,更重要的是,我们正在处理那些真的可能是完全意想不到的事情的情况。也许我们可以做得更好。

try {
  throw new Error('Oh no!')
} catch (error) {
  let message
  if (error instanceof Error) message = error.message
  else message = String(error)
  // we'll proceed, but let's report it
  reportError({message})
}

所以在这里,如果错误不是一个实际的Error 对象,那么我们将只是对错误进行字符串化处理,希望最终能成为有用的东西。

然后我们可以把它变成一个实用程序,在我们所有的catch块中使用。

function getErrorMessage(error: unknown) {
  if (error instanceof Error) return error.message
  return String(error)
}

const reportError = ({message}: {message: string}) => {
  // send the error to our logging service...
}

try {
  throw new Error('Oh no!')
} catch (error) {
  // we'll proceed, but let's report it
  reportError({message: getErrorMessage(error)})
}

这对我的项目很有帮助。希望它也能帮助你。

更新:Nicolas提出了一个很好的建议,用于处理你所处理的错误对象并不是一个真正的错误的情况。然后Jesse提出了一个建议,如果可能的话,将错误对象字符串化。因此,所有的综合建议看起来像这样。

type ErrorWithMessage = {
  message: string
}

function isErrorWithMessage(error: unknown): error is ErrorWithMessage {
  return (
    typeof error === 'object' &&
    error !== null &&
    'message' in error &&
    typeof (error as Record).message === 'string'
  )
}

function toErrorWithMessage(maybeError: unknown): ErrorWithMessage {
  if (isErrorWithMessage(maybeError)) return maybeError

  try {
    return new Error(JSON.stringify(maybeError))
  } catch {
    // fallback in case there's an error stringifying the maybeError
    // like with circular references for example.
    return new Error(String(maybeError))
  }
}

function getErrorMessage(error: unknown) {
  return toErrorWithMessage(error).message
}

很方便!

我认为这里的关键是要记住,虽然TypeScript有其有趣的部分,但不要因为你认为不可能或其他原因而否定TypeScript的编译错误或警告。大多数情况下,意外情况绝对有可能发生,TypeScript在强迫你处理这些不可能的情况方面做得很好......而且你可能会发现它们并不像你想象的那样不可能。