我遇到了一个非常奇怪的问题,即等待已将Promise传递resolve给事件-发射器回调的Promise会无错误地退出该过程。
resolve
const {EventEmitter} = require('events'); async function main() { console.log("entry"); let ev = new EventEmitter(); let task = new Promise(resolve=>{ ev.once("next", function(){resolve()}); console.log("added listener"); }); await task; console.log("exit"); } main() .then(()=>console.log("exit")) .catch(console.log); process.on("uncaughtException", (e)=>console.log(e));
我希望运行此程序时该过程将停止,因为当前显然从未发出过“ next”。但是我得到的输出是:
条目 添加了侦听器
然后nodejs进程正常终止。
我认为这是什么做的垃圾收集器,但ev并task显然还是在范围main。因此,我对如何完全退出该过程而没有错误感到无所适从。
ev
task
main
很显然,我 会 最终发出的事件,但我已经简化我的代码上面的重现。我在node v8.7.0。我的代码有问题吗?或者这是节点错误?
node v8.7.0
这个问题基本上是:节点如何决定退出事件循环还是再次发生?
基本上,节点保留已调度的异步请求(例如setTimeouts网络请求等)的参考计数。每调度一次,该计数就会增加,而每完成一次,该计数就会减少。如果到达事件循环周期的末尾,并且引用计数为零,则退出节点。
setTimeouts
简单地创建一个Promise或事件发射器并 不会 增加引用计数- 创建这些对象实际上并不是异步操作。例如,此promise的状态将始终处于待处理状态,但过程将立即退出:
const p = new Promise( resolve => { if(false) resolve() }) p.then(console.log)
同样,在创建发射器并注册侦听器之后,这也会退出:
const ev = new EventEmitter() ev.on("event", (e) => console.log("event:", e))
如果您希望Node等待从未计划的事件,那么您可能会想到Node不知道将来是否可能发生事件,但是这样做是因为每次计划一个事件时都会计数。
因此,请考虑以下小改动:
const ev = new EventEmitter() ev.on("event", (e) => console.log("event:", e)) const timer = setTimeout(() => ev.emit("event", "fired!"), 1000) // ref count is not zero, event loop will go again. // after timer fires ref count goes back to zero and node exits
作为附带说明,您可以使用以下命令删除对计时器的引用timeout.unref()。与前面的示例不同,这将立即退出:
timeout.unref()
const ev = new EventEmitter() ev.on("event", (e) => console.log("event:", e)) const timer = setTimeout(() => ev.emit("event", "fired!"), 1000) timer.unref()