好吧,让我们来谈谈这个
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在强迫你处理这些不可能的情况方面做得很好......而且你可能会发现它们并不像你想象的那样不可能。