我的理解是(unclustered)nodejs程序中的所有代码都在同一个线程中运行。鉴于此,我希望所有这些代码都作为同一个根事件循环的子代运行,因此如果检查了不同回调中运行的代码的堆栈跟踪,我们最终仍会回溯到同一个条目(该事件循环的“调度事件”行。但事实并非如此,我不明白为什么。
请考虑以下事项:
function printStackTrace() {
console.log(new Error().stack);
}
printStackTrace();
setTimeout(printStackTrace, 1000);
运行产生:
Error
at printStackTrace (/tmp/node/test.js:4:17)
at Object.<anonymous> (/tmp/node/test.js:7:1)
at Module._compile (module.js:446:26)
at Object..js (module.js:464:10)
at Module.load (module.js:353:32)
at Function._load (module.js:311:12)
at Array.0 (module.js:484:10)
at EventEmitter._tickCallback (node.js:190:39)
Error
at Object.printStackTrace [as _onTimeout] (/tmp/node/test.js:4:17)
at Timer.ontimeout (timers.js:94:19)
只需在REPL中运行console.log(new Error().stack);
,即可提供不同的根目录:
Error
at repl:1:13
at REPLServer.eval (repl.js:80:21)
at repl.js:190:20
at REPLServer.eval (repl.js:87:5)
at Interface.<anonymous> (repl.js:182:12)
at Interface.emit (events.js:67:17)
at Interface._onLine (readline.js:162:10)
at Interface._line (readline.js:426:8)
at Interface._ttyWrite (readline.js:603:14)
at ReadStream.<anonymous> (readline.js:82:12)
因此每个上的根(最下面)项是不同的(分别在EventEmitter,Timer和ReadStream中)。对于其他回调(例如网络)也是如此。
所以我想象一下
Error()
有特殊代码来隐藏它(作为不必要的实现细节)这些(如果有的话)是这种情况,并且通常在nodejs(edit:或v8)源中我可以读取实际根事件循环的实现吗?
答案 0 :(得分:1)
答案(或至少是线索)就在堆栈跟踪中。只需按照堆栈底部文件中的代码即可。
我不确定您正在使用的节点版本(0.6?更新时间!),但最新版本(0.10.17),
setTimeout(function() { console.log(new Error().stack) }, 1);
打印出来:
Error
at null._onTimeout (repl:1:38)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
让我们转到timers.js:110。此行位于listOnTimeout
函数内,该函数已分配给ontimeout
实例的Timer
属性。
Timer
is a C++ module interfaces与libuv;它是invokes ontimeout
函数的C ++代码。
所以有你的答案:堆栈的根是由C ++代码调用的JavaScript函数(无论是计时器还是流管道)。
由Error
提供给您的堆栈跟踪不会显示调用函数所涉及的任何本机代码。事件循环本身是由V8(本机代码)实现的,而不是JavaScript,因此你应该看到除了边界之外没有任何东西。
所以发生的事情非常接近你的第一次猜测。 JavaScript代码通过将某个属性设置为函数(或将函数作为参数传递给到本机代码)来注册回调。当C ++想要调用该函数时,它会获取引用并指示V8通过调用v8::Function::Call
来调用该函数。
如果您对V8如何工作感兴趣,embedder's guide是一个良好的开端。