我正在尝试编写一个函数,该函数将在引发对象文字时重新引入堆栈跟踪。 (See this related question)。
我注意到的是,如果将异步函数作为回调传递给另一个异步调用者函数,则如果调用者函数具有try / catch并捕获任何错误,并引发新的Error,则堆栈跟踪迷路。
我已经尝试了几种方法:
function alpha() {
throw Error("I am an error!");
}
function alphaObectLiberal() {
throw "I am an object literal!"; //Ordinarily this will cause the stack trace to be lost.
}
function syncFunctionCaller(fn) {
return fn();
}
function syncFunctionCaller2(fn) { //This wrapper wraps it in a proper error and subsequently preserves the stack trace.
try {
return fn();
} catch (err) {
throw new Error(err); //Stack trace is preserved when it is synchronous.
}
}
async function asyncAlpha() {
throw Error("I am also an error!"); //Stack trace is preseved if a proper error is thown from callback
}
async function asyncAlphaObjectLiteral() {
throw "I am an object literal!"; //I want to catch this, and convert it to a proper Error object.
}
async function asyncFunctionCaller(fn) {
return await fn();
}
async function asyncFunctionCaller2(fn) {
try {
await fn();
} catch (err) {
throw new Error(err);
}
}
async function asyncFunctionCaller3(fn) {
try {
await fn();
} catch (err) {
throw new Error("I'm an error thrown from the function caller!");
}
}
async function asyncFunctionCaller4(fn) {
throw new Error("No try catch here!");
}
async function everything() {
try {
syncFunctionCaller(alpha);
} catch (err) {
console.log(err);
}
try {
syncFunctionCaller2(alphaObectLiberal);
} catch (err) {
console.log(err);
}
try {
await asyncFunctionCaller(asyncAlpha);
} catch (err) {
console.log(err);
}
try {
await asyncFunctionCaller2(asyncAlphaObjectLiteral);
} catch (err) {
console.log(err); //We've lost the `everthing` line number from the stack trace
}
try {
await asyncFunctionCaller3(asyncAlphaObjectLiteral);
} catch (err) {
console.log(err); //We've lost the `everthing` line number from the stack trace
}
try {
await asyncFunctionCaller4(asyncAlphaObjectLiteral);
} catch (err) {
console.log(err); //This one is fine
}
}
everything();
输出:在堆栈跟踪中记录我的评论
[nodemon] starting `node src/index.js localhost 8080`
Error: I am an error!
at alpha (/sandbox/src/index.js:2:9)
at syncFunctionCaller (/sandbox/src/index.js:6:10)
at everything (/sandbox/src/index.js:43:5)
//We can see what function caused this error
at Object.<anonymous> (/sandbox/src/index.js:73:1)
at Module._compile (internal/modules/cjs/loader.js:776:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10)
at Module.load (internal/modules/cjs/loader.js:653:32)
at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
at Function.Module._load (internal/modules/cjs/loader.js:585:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:829:12)
Error: I am an object literal!
at syncFunctionCaller2 (/sandbox/src/index.js:17:11)
at everything (/sandbox/src/index.js:65:5)
//In a synchronous wrapper, the stack trace is preserved
at Object.<anonymous> (/sandbox/src/index.js:95:1)
at Module._compile (internal/modules/cjs/loader.js:776:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10)
at Module.load (internal/modules/cjs/loader.js:653:32)
at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
at Function.Module._load (internal/modules/cjs/loader.js:585:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:829:12)
at startup (internal/bootstrap/node.js:283:19)
Error: I am also an error!
at asyncAlpha (/sandbox/src/index.js:10:9)
at asyncFunctionCaller (/sandbox/src/index.js:18:16)
at everything (/sandbox/src/index.js:49:11)
//We can see what function caused this error
at Object.<anonymous> (/sandbox/src/index.js:73:1)
at Module._compile (internal/modules/cjs/loader.js:776:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10)
at Module.load (internal/modules/cjs/loader.js:653:32)
at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
at Function.Module._load (internal/modules/cjs/loader.js:585:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:829:12)
Error: I am an object literal!
at asyncFunctionCaller2 (/sandbox/src/index.js:25:11)
//We've lost the stacktrace in `everything`
at process._tickCallback (internal/process/next_tick.js:68:7)
at Function.Module.runMain (internal/modules/cjs/loader.js:832:11)
at startup (internal/bootstrap/node.js:283:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)
Error: I'm an error thrown from the function caller!
at asyncFunctionCaller3 (/sandbox/src/index.js:33:11)
//We've lost the stacktrace in `everything`
at process._tickCallback (internal/process/next_tick.js:68:7)
at Function.Module.runMain (internal/modules/cjs/loader.js:832:11)
at startup (internal/bootstrap/node.js:283:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)
Error: No try catch here!
at asyncFunctionCaller4 (/sandbox/src/index.js:38:9)
at everything (/sandbox/src/index.js:67:11)
//We can see what function caused this error
at process._tickCallback (internal/process/next_tick.js:68:7)
at Function.Module.runMain (internal/modules/cjs/loader.js:832:11)
at startup (internal/bootstrap/node.js:283:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)
[nodemon] clean exit - waiting for changes before restart
在我看来,wait语句正在使问题变得更糟。
这是怎么回事?
答案 0 :(得分:6)
缺少堆栈跟踪与Promise无关。编写相同的代码,它们具有以同步方式相互调用的函数,您将观察到完全相同的行为,即,在重新抛出new Error
时失去了完整的堆栈跟踪数据。只有Error
个对象提供堆栈访问。反过来,它由负责捕获交叉堆栈帧的堆栈跟踪的本机代码(如this of V8 engine)支持。每次创建Error
对象时,情况都会变得更糟,它会从此时开始跨堆栈帧捕获堆栈(至少在浏览器中是可见的,nodejs的实现可能有所不同)。这样,如果您捕获并回溯了不同的Error
对象,则在冒泡异常的顶部可以看到其堆栈跟踪。缺少Error
的异常链(无法将新的异常包裹在捕获的异常周围)使得很难填补这些空白。更有趣的是,ECMA-262 spec chapter 19.5根本没有引入Error.prototype.stack
属性,在MDN中,您反过来发现stack property是JS引擎非标准扩展。
编辑:关于堆栈上缺少“所有”功能,这是引擎如何将“异步/等待”转换为微任务调用以及谁真正在调用特定的回调的副作用。请参考V8引擎团队explanation以及他们的zero-cost async stack traces文档以了解详细信息。从版本12.x will incorporate开始的NodeJS更清晰的堆栈跟踪,可通过V8引擎提供的--async-stack-traces
选项获得。
答案 1 :(得分:0)
这可能不是直接的答案,但是我和我的团队正在建立一个库来处理异步/等待诺言,而无需try / catch块。
安装模块
[[lettuce, tomatoes, ginger, vodka, tomatoes]
[lettuce, tomatoes, flour, vodka, tomatoes]
...
[flour, tomatoes, vodka, vodka, mustard]
[[0, 1, 2, 3, 1]
[0, 1, 4, 3, 1]
...
[4, 1, 3, 3, 9]]
导入awaitCatcher
npm install await-catcher
使用它!
代替这样做:
const { awaitCatcher } = require("await-catcher")
现在您可以执行以下操作:
async function asyncFunctionCaller2(fn) {
try {
await fn();
} catch (err) {
throw new Error(err);
}
}
await-catcher库很简单。它返回一个带有两个索引的数组。
1)第一个索引包含结果/数据,如果有错误,则未定义
async function asyncFunctionCaller2(fn) {
let [ data, err ] = await awaitCatcher(fn);
// Now you can do whatever you want with data or error
if ( err ) throw err;
if ( data ) return data;
}
// Note:
// You can name the variables whatever you want.
// They don't have to be "data" or "err"
2)第二个索引包含错误;如果没有错误,则未定义
"[ data , undefined]"
Await-catcher还支持TypeScript中的类型。如果使用TypeScript,则可以传递要与返回值对照的类型。
示例:
"[undefined, error]"
我们将很快更新GitHub存储库以包含文档。 https://github.com/canaanites/await-catcher
编辑:
似乎V8引擎在开始新的滴答声时正在“丢失”错误堆栈跟踪。它仅从该点返回错误堆栈。有人回答了类似的问题here。
将代码更改为此: https://codesandbox.io/embed/empty-wave-k3tdj
interface promiseType {
test: string
}
(async () => {
let p = Promise.resolve({test: "hi mom"})
let [ data , error ] = await awaitCatcher<promiseType>(p);
console.log(data, error);
})()
结论
最好不是抛出Error对象而是抛出字符串。这将更加难以调试,并可能导致丢失错误堆栈跟踪。强烈建议您阅读以下内容:Throwing strings instead of Errors
答案 2 :(得分:-1)
编辑:此答案似乎完全不正确,请参阅@andy的答案,其中确切描述了这里发生的情况。
我认为上下文并没有完全丢失-它从未出现过。您正在使用async / await,并且您的代码有效地分成了“块”,这些块以某种非线性的方式执行-异步执行。这意味着解释器在某些时候会离开主线程,进行“滴答”(因此您在堆栈跟踪中看到process._tickCallback
)并执行下一个“块”。
为什么会这样?因为async / await是Promise
的语法糖,所以它可以有效地包装由外部事件引导的回调(我相信在这种情况下,它是一个计时器)。
您能对此做什么?也许不能肯定地说从来没有那样。但我认为以下是一个好的开始:https://github.com/nodejs/node/issues/11865