【问题标题】:How to chain exceptions in javascript (ie add cause like in java)如何在javascript中链接异常(即在java中添加原因)
【发布时间】:2013-07-27 01:02:10
【问题描述】:

是否有标准/最佳实践方法可以在 javascript 中添加异常原因。在java中你可以这样做:

Throwable t = new Exception("whatever");
t.addCause(previouslyCaughtException);
throw t;

当结果异常被打印出来时,它会给你一个很好的跟踪,包括原因。有什么好的方法可以在 javascript 中做到这一点,还是我必须自己动手?

【问题讨论】:

    标签: java javascript node.js error-handling


    【解决方案1】:

    目前(直到有更好的答案),这就是我所做的:

    ...
    } catch(e) {
      throw new Error("My error message, caused by: "+e.stack+"\n ------The above causes:-----")
    }
    

    我打印异常的方式使它看起来漂亮而干净:

    console.log(e.stack)
    

    打印如下内容:

    My error message: SomeError
    <some line>
    <more lines>
    ------The above causes:-----
    <some line>
    <more lines>
    

    如果它说“原因”可能会更好,因为首先打印导致错误的异常的堆栈跟踪。

    【讨论】:

    • 我建议添加原始错误消息。尽管如此,还是感谢您的提示!
    • 不错,小而简单 - 喜欢它!我的版本:module.exports = function (msg, causeE) { return new Error(`${causeE.message}: ${causeE.stack}\n ----------- CAUSES -----------\n${msg}`) }
    【解决方案2】:

    如果你使用的是 Node.js,你可以使用 VError

    Joyent 还有一个页面讨论 Node.js 中错误处理的最佳实践https://www.joyent.com/developers/node/design/errors

    唯一的一点是,如果你传入一个 null 或未定义的参数,我不喜欢 Joyent 的 VError 实现如何失败。当您处理错误时,这一点尤其重要,因为它只会掩盖问题的根本原因。我已经分叉了他们的 VError,因此它不会因 null 或 undefined 参数而失败。 https://github.com/naddison36/node-verror

    【讨论】:

      【解决方案3】:

      在产品中,我们使用TraceError

      用法

      import TraceError from 'trace-error';
      
      global.TraceError = TraceError; // expose globally (optional)
      
      throw new TraceError('Could not set status', srcError, ...otherErrors);
      

      输出

      【讨论】:

      • 噢,我喜欢这个。你应该为此制作一个 github repo 和 npm 包。我可以看到一些有用的改进(比如消除 otherErrors 异常之间的重复堆栈跟踪行并摆脱堆栈的内部 TraceError 部分)。我必须在某个时候尝试一下!
      • @BT 很高兴你喜欢它 :) 我已根据你的要求将它作为 npm 模块和 git repo 发布。我也更新了答案。让我知道如何改进。也许是公关?我不确定您所说的duplicate stack-trace lines 是什么意思
      • 通过“重复堆栈跟踪行”我的意思是在大多数情况下,您捕获一个异常然后抛出相关异常,原始异常的堆栈跟踪有许多相同的行新创建的异常,并且可以简化输出以利用它。
      • 我不完全确定我理解你的意思,但我建议组合堆栈跟踪的相似部分,而不是删除两个相似部分。因此,您仍然可以获得所有堆栈信息,但您只需删除重复的部分。
      • 老实说,我还没有机会尝试这个,但我接受它作为答案,因为它有所有正确的想法,而且你费了很大力气把它作为一个我们都可以从中受益的模块!
      【解决方案4】:

      您可以将错误对象Error 链接起来,将stackmessage 串联起来。

      var console = {
          log: function(s) {
            document.getElementById("console").innerHTML += s + "<br/>"
          }
        }
      
      var error1=new Error("This is error 1");
      console.log("Message: ".concat( error1.message ));
      console.log("Stack<br>".concat(error1.stack) );
      
      var error2=new Error("This is error 2");
      console.log("Message: ".concat( error2.message) );
      console.log("Stack<br>".concat( error2.stack) );
      
      var error3=new Error("This is error 3");
      error3.stack=error3.stack.concat(error2.stack).concat(error1.stack)
      console.log("Message: ".concat(error3.message));
      console.log("Stack<br>".concat(error3.stack));
      &lt;div id="console" /&gt;

      【讨论】:

        【解决方案5】:

        tl;dr 这不是解决方案,只是 ECMA 脚本采用某些标准之前的帮手。

        编辑:我将此答案包装到 chainable-error npm 包中。

        嗯,这是一个困难的话题。原因是,ECMA 脚本定义中没有关于堆栈跟踪的定义(甚至在ES9 / ES2019) 中也没有)。所以一些引擎实现了他们自己的堆栈跟踪及其表示的想法。

        他们中的许多人已经实现了Error.prototype.stack 属性,它是堆栈跟踪的字符串表示形式。由于未定义,因此您不能依赖字符串格式。幸运的是,V8 引擎很常见(Google Chrome 和 NodeJS),这让我们有机会至少尝试一下。

        V8(以及使用它的应用程序)的一个好处是堆栈跟踪具有通用格式:

        /path/to/file/script.js:11
                throw new Error("Some new Message", e);
                ^
        
        Error: Some new Message
            at testOtherFnc (/path/to/file/script.js:69:15)
            at Object.<anonymous> (/path/to/file/script.js:73:1)
            at Module._compile (internal/modules/cjs/loader.js:688:30)
            at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
            at Module.load (internal/modules/cjs/loader.js:598:32)
            at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
            at Function.Module._load (internal/modules/cjs/loader.js:529:3)
            at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
            at startup (internal/bootstrap/node.js:285:19)
            at bootstrapNodeJSCore (internal/bootstrap/node.js:739:3)
        

        ...并且堆栈跟踪不会在控制台中解析和设置样式。

        这给了我们一个很好的机会来链接它们(或者至少改变错误产生的输出)。

        一个非常简单的方法是这样的:

        let ff = v => JSON.stringify(v, undefined, 4);
        const formatForOutput = v => {
            try {
                return ff(v).replace(/\n/g, '\n    ');
            } catch (e) {
                return "" + v;
            }
        };
        
        const chainErrors = exporting.chainErrors = (e1, e2) => {
            if (e1 instanceof Error)
                e2.stack += '\nCaused by: ' + e1.stack;
            else
                e2.stack += '\nWas caused by throwing:\n    ' + formatForOutput(e1);
        
            return e2;
        }
        

        你可以这样使用:

        function someErrorThrowingFunction() {
            throw new Error("Some Message");
        }
        
        function testOtherFnc() {
            try {
                someErrorThrowingFunction();
            } catch (e) {
                throw chainErrors(e, new Error("Some new Message"));
            }
        }
        

        产生:

        /path/to/file/script.js:11
                throw new Error("Some new Message", e);
                ^
        
        Error: Some new Message
            at testOtherFnc (/path/to/file/script.js:11:15)
            at Object.<anonymous> (/path/to/file/script.js:15:1)
            at Module._compile (internal/modules/cjs/loader.js:688:30)
            at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
            at Module.load (internal/modules/cjs/loader.js:598:32)
            at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
            at Function.Module._load (internal/modules/cjs/loader.js:529:3)
            at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
            at startup (internal/bootstrap/node.js:285:19)
            at bootstrapNodeJSCore (internal/bootstrap/node.js:739:3)
        Caused by: Error: Some Message
            at someErrorThrowingFunction (/path/to/file/script.js:4:11)
            at testOtherFnc (/path/to/file/script.js:9:9)
            at Object.<anonymous> (/path/to/file/script.js:15:1)
            at Module._compile (internal/modules/cjs/loader.js:688:30)
            at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
            at Module.load (internal/modules/cjs/loader.js:598:32)
            at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
            at Function.Module._load (internal/modules/cjs/loader.js:529:3)
            at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
            at startup (internal/bootstrap/node.js:285:19)
        

        这与 Java 生成的堆栈跟踪非常相似。 这存在三个问题。

        第一个问题是调用站点的重复,可以解决但复杂。

        第二个是生成的输出依赖于引擎,这种尝试对于 V8 非常有效,但不适用于 Firefox,例如,因为 Firefox 不仅使用另一种样式,而且它们还解析和设置错误消息的样式,这会阻止我们像这样链接它。

        第三个问题是可用性。这有点笨拙,你必须记住这个功能,如果你在正确的引擎中,你需要跟踪。另一种方法是这样的:

        const Error = (() => {
            const glob = (() => { try { return window; } catch (e) { return global; } })();
        
            const isErrorExtensible = (() => {
                try {
                    // making sure this is an js engine which creates "extensible" error stacks (i.e. not firefox)
                    const stack = (new glob.Error('Test String')).stack;
                    return stack.slice(0, 26) == 'Error: Test String\n    at ';
                } catch (e) { return false; }
            })();
        
            const OriginalError = glob.Error;
        
            if (isErrorExtensible) {
                let ff = v => JSON.stringify(v, undefined, 4);
                const formatForOutput = v => {
                    try {
                        return ff(v).replace(/\n/g, '\n    ');
                    } catch (e) {
                        return "" + v;
                    }
                };
        
                const chainErrors = (e1, e2) => {
                    if (e1 instanceof OriginalError)
                        e2.stack += '\nCaused by: ' + e1.stack;
                    else
                        e2.stack += '\nWas caused by throwing:\n    ' + formatForOutput(e1);
        
                    return e2;
                }
        
                class Error extends OriginalError {
                    constructor(msg, chained) {
                        super(msg);
        
                        if (arguments.length > 1)
                            chainErrors(chained, this);
                    }
                }
        
                return Error;
            } else
                return OriginalError; // returning the original if we can't chain it
        })();
        

        然后你就可以像在 Java 中那样做:

        function someErrorThrowingFunction() {
            throw new Error("Some Message");
        }
        
        function testOtherFnc() {
            try {
                someErrorThrowingFunction();
            } catch (e) {
                throw new Error("Some new Message", e);
            }
        }
        
        testOtherFnc();
        

        尽管第二个版本带来了一些(其他)问题,但它可能是“更容易”的一个,因为即使引擎不支持链接,您也不需要更改代码,因为您可以提供一个函数(错误构造函数)任意数量的参数。

        无论如何,希望这对 ES2020 有用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-05-28
          • 2019-12-24
          • 1970-01-01
          • 2015-07-08
          • 1970-01-01
          • 2014-05-19
          相关资源
          最近更新 更多