ES2022 在
Error
对象中添加了
.cause
非可枚举属性,该属性可以从构造函数中设置。
function doThing(){
try {
throw new Error('Internal Error')
}
catch (err) {
throw new Error('Do thing failed!', { cause: err })
}
}
try {
doThing()
}
catch (err) {
console.log('Error: ', err)
console.log('Cause: ', err.cause)
}
支持的语言:
Chrome > 93
Firefox > 91
Safari > 15
Node.js > v16.9.0
从
tc39提案中,可以看到一个
es-shim Error
实现以及如何设置
cause属性。
更多堆栈跟踪细节请参见
Scotty Jamison的答案。
2022年之前的回答
我不知道有没有像Java那样的本地方法,也没有找到一个优雅的解决方案来包装错误。
创建一个new Error
的问题是你可能会丢失附加到原始Error
的元数据,堆栈跟踪和类型通常是重要的丢失项。
对已经抛出的错误进行修改会更快,但仍然有可能将错误的数据修改得无影无踪。在其他地方创建的错误中进行探索感觉也不太对。
创建一个新的错误和新的堆栈
一个新的Error
的.stack
属性是一个普通的字符串,可以在抛出之前修改为你想要的内容。但是完全替换错误的stack
属性可能会导致调试时非常混乱。
当原始抛出的错误和错误处理程序位于不同的位置或文件中(这在使用Promise时很常见),你可能能够追踪到原始错误的来源,但无法追踪到实际捕获错误的处理程序所在的位置。为了避免这种情况,最好在stack
中保留对原始错误和新错误的引用。如果原始错误中还存储了其他元数据,那么访问完整的原始错误也会非常有用。
下面是一个捕获错误、将其包装在新错误中并添加原始stack
并存储error
的示例:
try {
throw new Error('First one')
} catch (error) {
let e = new Error(`Rethrowing the "${error.message}" error`)
e.original_error = error
e.stack = e.stack.split('\n').slice(0,2).join('\n') + '\n' +
error.stack
throw e
}
哪个抛出:
/so/42754270/test.js:9
throw e
^
Error: Rethrowing the "First one" error
at test (/so/42754270/test.js:5:13)
Error: First one
at test (/so/42754270/test.js:3:11)
at Object.<anonymous> (/so/42754270/test.js:13:1)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)
at Module.runMain (module.js:604:10)
at run (bootstrap_node.js:394:7)
at startup (bootstrap_node.js:149:9)
所以我们创建了一个新的通用错误。不幸的是,原始错误的类型在输出中被隐藏了,但是错误已经作为“.original_error”附加了起来,因此仍然可以访问。新的堆栈已经大部分被移除,除了生成行是重要的,还有原始错误的堆栈被附加上去。
任何试图解析堆栈跟踪的工具可能无法与此更改配合使用,或者最好的情况下,它们会检测到两个错误。
使用ES2015+错误类重新抛出
将其转换为可重用的ES2015+错误类:
class RethrownError extends Error {
constructor(message, error){
super(message)
this.name = this.constructor.name
if (!error) throw new Error('RethrownError requires a message and error')
this.original_error = error
this.stack_before_rethrow = this.stack
const message_lines = (this.message.match(/\n/g)||[]).length + 1
this.stack = this.stack.split('\n').slice(0, message_lines+1).join('\n') + '\n' +
error.stack
}
}
throw new RethrownError(`Oh no a "${error.message}" error`, error)
结果为
/so/42754270/test2.js:31
throw new RethrownError(`Oh no a "${error.message}"" error`, error)
^
RethrownError: Oh no a "First one" error
at test (/so/42754270/test2.js:31:11)
Error: First one
at test (/so/42754270/test2.js:29:11)
at Object.<anonymous> (/so/42754270/test2.js:35:1)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)
at Module.runMain (module.js:604:10)
at run (bootstrap_node.js:394:7)
at startup (bootstrap_node.js:149:9)
那么,你知道每当你看到一个 RethrownError
时,原始错误仍然可以在 .original_error
中找到。
这种方法并不完美,但它意味着我可以将底层模块中已知的错误重新输入到通用类型中,以便更容易处理,通常使用 bluebirds 的 filtered catch .catch(TypeError, handler)
。
注意 这里的 stack
变为可枚举。
带有修改堆栈的相同错误
有时候你需要保留原始错误的大部分内容。
在这种情况下,你可以将新信息追加/插入到现有堆栈中。
file = '/home/jim/plumbers'
try {
JSON.parse('k')
} catch (e) {
let message = `JSON parse error in ${file}`
let stack = new Error(message).stack
e.stack = e.stack + '\nFrom previous ' + stack.split('\n').slice(0,2).join('\n') + '\n'
throw e
}
返回的是
/so/42754270/throw_error_replace_stack.js:13
throw e
^
SyntaxError: Unexpected token k in JSON at position 0
at Object.parse (native)
at Object.<anonymous> (/so/42754270/throw_error_replace_stack.js:8:13)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)
at Module.runMain (module.js:604:10)
at run (bootstrap_node.js:394:7)
at startup (bootstrap_node.js:149:9)
From previous Error: JSON parse error in "/home/jim/plumbers"
at Object.<anonymous> (/so/42754270/throw_error_replace_stack.js:11:20)
请注意,堆栈处理是简单的,并假设错误消息是单行的。如果遇到多行错误消息,您可能需要查找
\n at
来终止消息。