节点退出没有错误,不等待承诺(事件回调)



我遇到了一个非常奇怪的问题,等待一个将其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进程优雅地终止。

我认为这与垃圾收集器有关,但evtask显然仍在main的作用域中。所以我真的不知道为什么这个过程完全没有错误地退出。

显然,我最终会发出事件,但我已经将代码简化为上面的代码来重现。我在node v8.7.0。我的代码有问题吗?或者这是节点错误吗?

这个问题基本上是:节点如何决定是退出事件循环还是再次循环?

基本上,node保持一个已调度异步请求的参考计数——setTimeouts、网络请求等。每次调度一个,该计数就会增加,每次完成一个,计数就会减少。如果到达事件循环周期的末尾,并且引用计数为零,则节点退出。

简单地创建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在这里对事件循环进行了很好的讨论,澄清了许多误解:https://www.youtube.com/watch?v=PNa9OMajw9w

我调试了几个小时,为什么我们的一个脚本在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"one_answers"DOING SOMETHING ELSE",如果您使用第一个,则会打印

一般来说,您的代码组合了三种类似但不同的方法:async/await、promise和事件侦听器。我不知道你说的"炸弹爆炸"是什么意思。但看看代码,结果似乎是意料之中的。

您的进程退出,因为您在添加事件侦听器时调用了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))

最新更新