不幸的是,在 Javascript 中没有“官方”的方式来实现这个基本功能。我将分享我在不同包中看到的三种最常见的解决方案,以及如何在现代 Javascript (es6+) 中实现它们,以及它们的一些优缺点。
1.继承Error类
在 es6 中对“Error”的实例进行子类化变得更加容易。只需执行以下操作:
class FileNotFoundException extends Error {
constructor(message) {
super(message)
// Not required, but makes uncaught error messages nicer.
this.name = 'FileNotFoundException'
}
}
完整示例:
class FileNotFoundException extends Error {
constructor(message) {
super(message)
// Not required, but makes uncaught error messages nicer.
this.name = 'FileNotFoundException'
}
}
// Example usage
function readFile(path) {
throw new FileNotFoundException(`The file ${path} was not found`)
}
try {
readFile('./example.txt')
} catch (err) {
if (err instanceof FileNotFoundException) {
// Handle the custom exception
console.log(`Could not find the file. Reason: ${err.message}`)
} else {
// Rethrow it - we don't know how to handle it
// The stacktrace won't be changed, because
// that information is attached to the error
// object when it's first constructed.
throw err
}
}
如果您不喜欢设置this.name
为硬编码字符串,则可以将其设置为this.constructor.name
,这将给出您的类的名称。这样做的好处是您的自定义异常的任何子类都不需要更新this.name
,this.constructor.name
子类的名称也不需要更新。
与某些替代解决方案相比,子类异常的优势在于它们可以提供更好的编辑器支持(例如自动完成)。您可以轻松地向特定异常类型添加自定义行为,例如附加函数、替代构造函数参数等。在提供自定义行为或数据时,支持 typescript 也往往更容易。
有很多关于如何正确地子类化的讨论Error
。例如,如果您使用的是转译器,则上述解决方案可能不起作用。有些人建议使用特定于平台的 captureStackTrace() 如果它可用(虽然我在使用它时没有注意到错误有任何不同 - 也许它不再相关了🤷♂️)。要了解更多信息,请参阅此MDN 页面和此Stackoverflow 答案。
许多浏览器 API 走这条路并抛出自定义异常(可以在这里看到)
2. 给Error增加一个区别属性
这个想法很简单。创建您的错误,为您的错误添加一个额外的属性,例如“代码”,然后抛出它。
const error = new Error(`The file ${path} was not found`)
error.code = 'NotFound'
throw error
完整示例:
function readFile(path) {
const error = new Error(`The file ${path} was not found`)
error.code = 'NotFound'
throw error
}
try {
readFile('./example.txt')
} catch (err) {
if (err.code === 'NotFound') {
console.log(`Could not find the file. Reason: ${err.message}`)
} else {
throw err
}
}
当然,您可以创建一个辅助函数来删除一些样板文件并确保一致性。
此解决方案的优点是您无需导出包可能引发的所有可能异常的列表。您可以想象,例如,如果您的包一直使用 NotFound 异常来指示特定函数无法找到预期资源,那会变得多么尴尬。您想要添加一个 addUserToGroup() 函数,该函数理想情况下会根据未找到的资源抛出 UserNotFound 或 GroupNotFound 异常。对于子类异常,您将不得不做出棘手的决定。使用错误对象上的代码,您就可以做到。
这是路由节点的 fs module对异常的处理。如果您试图读取一个不存在的文件,它会抛出一个带有一些附加属性的错误实例,例如code
,它将"ENOENT"
为该特定异常设置为。
3. 返回您的异常。
谁说你必须扔它们?在某些情况下,返回出错的地方可能最有意义。
function readFile(path) {
if (itFailed()) {
return { exCode: 'NotFound' }
} else {
return { data: 'Contents of file' }
}
}
在处理大量异常时,像这样的解决方案可能最有意义。这很容易做到,并且可以帮助自我记录哪些函数给出了哪些异常,这使得代码更加健壮。缺点是它会给你的代码增加很多膨胀。
完整示例:
function readFile(path) {
if (Math.random() > 0.5) {
return { exCode: 'NotFound' }
} else {
return { data: 'Contents of file' }
}
}
function main() {
const { data, exCode } = readFile('./example.txt')
if (exCode === 'NotFound') {
console.log('Could not find the file.')
return
} else if (exCode) {
// We don't know how to handle this exCode, so throw an error
throw new Error(`Unhandled exception when reading file: ${exCode}`)
}
console.log(`Contents of file: ${data}`)
}
main()
非解决方案
其中一些解决方案感觉需要做很多工作。只是抛出一个对象文字很诱人,例如throw { code: 'NotFound' }
. 不要这样做!堆栈跟踪信息附加到错误对象。如果这些对象文字中的一个曾经漏过并成为未捕获的异常,您将无法通过堆栈跟踪来知道它在哪里或如何发生。一般来说,调试会困难得多。
奖励:显式异常
当您的包开始处理大量异常时,我建议使用如上所述的“返回异常”方法,以帮助您跟踪哪些异常可能来自何处(至少在您的包内部使用它 - 您仍然可以为您的包裹用户扔东西)。有时,即使是这种解决方案也不够好。我整理了一些显式异常包来帮助处理这些场景(可以在此处找到。还有一个轻量版本,您可以在此处复制粘贴到您的项目中)。这个想法是要求用户明确列出他们希望函数调用提供哪些异常。这使得跟踪异常路径变得非常容易。
try {
// readFile() will give an exception.
// In unwrap(), you list which exceptions you except, to help with self-documentation.
// runtime checks help ensure no other kinds of exceptions slip through.
return unwrap(readFile('./example.txt'), ['NotFound'])
} catch (err) {
// Handle the exception here
}
完整示例:
// You must have the explicit-exceptions package for this code to run
const { Exception, wrap, unwrap } = require('explicit-exceptions')
const readFile = wrap(path => {
throw new Exception('NotFound')
})
function main () {
try {
return unwrap(readFile('./example.txt'), ['NotFound'])
} catch (ex) {
if (!(ex instanceof Exception)) throw ex // Not an exception, don't handle it
console.assert(ex.code === 'NotFound')
console.log('File not found')
}
}
main()