在NodeJS中重新抛出异常而不会丢失堆栈跟踪

时间:2017-03-12 23:06:16

标签: javascript node.js exception-handling expect.js

如何在nodejs / javascript中重新抛出错误或异常并包含自定义消息。

我有以下代码

var json = JSON.parse(result);

我希望在发生任何解析错误时将result内容包含在异常消息中。这样的事情。

1.  try {
2.    var json = JSON.parse(result);
3.    expect(json.messages.length).to.be(1);
4.  } catch(ex) {
5.    throw new Error(ex.message + ". " + "JSON response: " + result);
6.  }

这里的问题是我丢失了堆栈跟踪。

有没有办法与java类似?

throw new Error("JSON response: " + result, ex);

4 个答案:

答案 0 :(得分:29)

我不知道像Java这样的原生方法,但我还没有找到一个包装错误的优雅解决方案。

创建new Error的问题是,您可能会丢失附加到抛出的原始Error的元数据,堆栈和类型通常会留下什么。

对现有错误进行修改的速度更快,但仍然可以修改错误中的数据。在其他地方创建的错误中,它也是错误的。

创建新错误和新堆栈

可以修改新.stack的{​​{1}}属性,以便在抛出之前说出您喜欢的内容。完全替换错误Error属性可能会让调试变得非常混乱。

当原始抛出错误和错误处理程序位于不同的位置或文件中时,您可能能够跟踪原始错误的来源,但不能跟踪实际捕获错误的处理程序。为了避免这种情况,最好在stack中保留对这两个错误的引用。如果存储了其他元数据,则访问完整的原始错误也很有用。

以下是捕获错误的示例,将其包装在新错误中,但添加原始stack并存储stack

error

引发:

try {
  throw new Error('First one')
} catch (error) {
  let e = new Error(`Rethrowing the "${error.message}" error`)
  e.original = 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) 。遗憾的是,原始错误的类型从输出中隐藏,但Error已作为error附加,因此仍可以访问它。除了重要的生成行以及附加的原始错误.original之外,新的stack已基本删除。

任何尝试解析堆栈跟踪的工具都可能无法使用此更改或最佳情况,它们会检测到两个错误。

ES2015错误类

将其变成可重复使用的ES2015 +错误类......

stack

结果

// Standard error extender from @deployable/errors

class ExtendedError extends Error {
  constructor(message){
    super(message)
    this.name = this.constructor.name
    this.message = message
    if (typeof Error.captureStackTrace === 'function'){
      Error.captureStackTrace(this, this.constructor)
    } else {
      this.stack = (new Error(message)).stack
    }
  }
}

class RethrownError extends ExtendedError {
  constructor(message, error){
    super(message)
    if (!error) throw new Error('RethrownError requires a message and error')
    this.original = error
    this.new_stack = this.stack
    let 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仍然可以使用原始错误。

这种方法并不完美,但这意味着我可以将基础模块中的已知错误输入到更容易处理的泛型类型中,通常使用蓝鸟filtered catch .catch(TypeError, handler)

修改堆栈的相同错误

有时您需要保持原始错误。

在这种情况下,您只需将新信息附加/插入现有堆栈即可。

.original

返回

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) 来终止消息。

答案 1 :(得分:6)

如果您只想更改消息,则只需更改消息:

&#13;
&#13;
try {
  throw new Error("Original Error");
} catch(err) {
  err.message = "Here is some context -- " + err.message;
  throw err;
}
&#13;
&#13;
&#13;

答案 2 :(得分:2)

您也可以继续将错误抛到试用链上。如果你想修改任何东西:b。

中的throw语句之前

function a() {
    throw new Error('my message');
}

function b() {
    try {
        a();
    } catch (e) {
        // add / modify properties here
        throw e;
    }
}

function c() {
    try {
        b();
    } catch (e) {
        console.log(e);
        document.getElementById('logger').innerHTML = e.stack;
    }
}
c();
<pre id="logger"></pre>

答案 3 :(得分:0)

您可能想看看Joyent的verror module,它提供了一种包装错误的简便方法:

var originError = new Error('No such file or directory');
var err = new VError(originError, 'Failed to load configuration');
console.error(err.message);

这将打印:

Failed to load configuration: No such file or directory