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 的东西。