节点退出时没有错误并且不等待promise(事件回调)

问题描述 投票:0回答:4

我遇到了一个非常奇怪的问题,即等待已将其

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
。我的代码有问题还是节点错误?

javascript node.js promise
4个回答
54
投票

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

基本上,节点会保留已调度异步请求的引用计数 -

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 对事件循环进行了很好的讨论,消除了很多误解:

https://www.youtube.com/watch?v=PNa9OMajw9w


4
投票
我调试了几个小时,为什么我们的一个脚本在

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”


0
投票
在处理基于复杂事件的算法时,您可能会发现此解决方法很有用。在算法开始之前的某个地方,输入:

setInterval(() => {}, 30000);


这将阻止节点退出。


-3
投票
一般来说,您的代码组合了三种相似但不同的方法:异步/等待、承诺、事件侦听器。我不确定你所说的“炸弹”是什么意思。但看代码,结果似乎是预料之中的。

您的进程退出,因为您在添加事件侦听器时调用了 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))
    
© www.soinside.com 2019 - 2024. All rights reserved.