我遇到了一个非常奇怪的问题,即等待已将其
resolve
传递给事件发射器回调的 Promise 只会退出进程而不会出现错误。
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));
当我运行此命令时,我预计该过程会停止,因为显然当前从未发出“下一个”。但我得到的输出是:
进入
添加了监听器
然后nodejs进程正常终止。
我认为这与垃圾收集器有关,但
ev
和 task
显然仍在 main
的范围内。所以我真的不知道为什么这个过程完全没有错误地退出。
显然我会最终会发出该事件,但我已将代码简化为上面的代码以进行重现。我在
node v8.7.0
。我的代码有问题还是节点错误?
这个问题基本上是:节点如何决定是退出事件循环还是再次循环?
基本上,节点会保留已调度异步请求的引用计数 -
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()
。与前面的示例不同,这将立即退出:
const ev = new EventEmitter()
ev.on("event", (e) => console.log("event:", e))
const timer = setTimeout(() => ev.emit("event", "fired!"), 1000)
timer.unref()
Bert Belder 对事件循环进行了很好的讨论,消除了很多误解:
main
函数中间的一行代码之后退出(没有任何错误)。这是一条线
await connectToDatabase(config)
。你知道吗?我发现这两个功能之间的区别至关重要:
第一:
async function connectToDatabase(config = {}) {
if (!config.port) return;
return new Promise(resolve => {
resolve();
})
}
第二:
async function connectToDatabase(config = {}) {
return new Promise(resolve => {
if (!config.port) return;
resolve();
})
}
第二个函数有时(当 config.port 为空时)会创建从未解决的 Promise,它使事件循环为空,并且 Node.js 退出时认为“这里没什么可做的”自己检查一下:
// index.js - start it as node index.js
(async function main() {
console.log('STARTED')
await connectToDatabase()
console.log('CONNECTED')
console.log('DOING SOMETHING ELSE')
})()
如果您使用第二个功能,则不会打印“CONNECTED”和“DOING SOMETHING ELSE”;如果您使用第一个功能,则不会打印“CONNECTED”和“DOING SOMETHING ELSE”
setInterval(() => {}, 30000);
您的进程退出,因为您在添加事件侦听器时调用了 Promise。它成功解析,因此退出。如果您尝试记录任务,它会给您未定义的信息。不要在 then 语句中记录“exit”,而是记录结果。任务将是未定义的,因为程序不会等待解析其值并且其“代码块已完成”。
您可以将代码简化为以下内容。正如您所看到的,自从您调用解析函数后,它立即解析。
const { EventEmitter } = require('events');
let ev = new EventEmitter()
var p = new Promise(( resolve ) => {
ev.once("next", resolve("Added Event Listener"));
})
p
.then(res => console.log(res))
.catch(e => console.log(e))