我有一个非常奇怪的问题,等待一个已经通过其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));
我希望这个过程在我运行时暂停,因为很明显" next"目前从未发出过。但我得到的输出是:
条目
添加了听众
然后nodejs进程正常终止。
我认为这与垃圾收集器有关,但ev
和task
显然仍在main
范围内。所以我真的不知道为什么这个过程完全没有错误地退出。
显然我会最终发出这个事件,但我已经将我的代码简化为上面的内容来重现。我在node v8.7.0
。我的代码有问题或这是一个节点错误吗?
答案 0 :(得分:5)
这个问题基本上是:节点如何决定是退出事件循环还是再次出现?
基本上节点保留计划的异步请求的引用计数 - setTimeouts
,网络请求等。每次计划一次,该计数增加,每次完成一次,计数减少。如果到达事件循环周期结束并且引用计数为零节点退出。
简单地创建承诺或事件发射器不增加引用计数 - 创建这些对象实际上不是异步操作。例如,此承诺的状态将始终处于暂挂状态,但该过程会立即退出:
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在这里有一个很好的关于事件循环的讨论清除了很多误解:https://www.youtube.com/watch?v=PNa9OMajw9w
答案 1 :(得分:0)
作为一般说明,您的代码组合了三种相似但不同的方法:async / await,promises,event listeners。我不确定你所说的“炸弹”是什么意思。但看看代码,结果似乎是预期的。
您的进程退出,因为您在添加事件侦听器时调用了promise。它成功解决,因此退出。如果您尝试记录任务,它将为您提供未定义的。而不是在then语句中记录“exit”,而是记录结果。任务将是未定义的,因为程序不等待解析其值并且其“代码块已完成”。
您可以将代码简化为以下内容。您可以看到,因为您调用了resolve函数,它会立即解析。
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))
答案 2 :(得分:0)
我调试了几个小时,为什么我们的一个脚本在 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 为空时)创建从未解决的承诺,它使事件循环为空,并且 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' 不会在你使用第二个函数时被打印出来,如果你使用第一个函数则会被打印