节点无错误退出且不等待承诺(事件回调)
Posted
技术标签:
【中文标题】节点无错误退出且不等待承诺(事件回调)【英文标题】:Node exits without error and doesn't await promise (Event callback) 【发布时间】:2018-04-05 10:58:55 【问题描述】:我遇到了一个非常奇怪的问题,即等待已将其 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
。我的代码有问题还是这是一个节点错误?
【问题讨论】:
hmm 我怀疑答案是,一旦所有回调都设置好了,虽然技术上都正确,但 没有代码在运行了。 “完全轰炸”是什么意思? “进程停止”(您所期望的)和“进程完全退出而没有错误”之间有什么区别? 清除了;它正在优雅地退出。我很确定这个问题的答案是没有任何东西在运行,也没有打开系统事件/文件,所以节点认为程序完成。 所以“暂停”是指“挂起而不退出”? 是的,只是创建一个永不解决的承诺(就像调用main()
所做的那样)并不能阻止节点退出 - 它只是被垃圾收集。需要有一个实时的全局回调,例如 setTimeout
,它仍然引用承诺并且可以解决它(但永远不会)。
【参考方案1】:
这个问题基本上是:node是如何决定是退出事件循环还是重新循环?
基本上,节点会保留已调度异步请求的引用计数——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
【讨论】:
@Mark Meyer - 非常有用的答案,谢谢,让整个问题非常清楚。关于这一点:Basically node keeps a reference count of scheduled async requests. setTimeouts, network requests, etc
——你知道这包括什么有严格的定义吗?它基本上是与节点的核心/本机模块异步的吗?
你能让你的第一个例子(Promise 例子)等一下吗?
@SohailSi,您可以添加一个 setTimeout 来解决第一个示例中的承诺。然后它将等待计时器设置的时间。
很好的答案。您是否了解这在浏览器中是如何发挥作用的?例如v8?或者您在文本中引用节点的所有时间(例如节点保持引用计数、节点退出等),它也可以解释为 v8(例如 v8 保持引用计数、v8 退出等)?所以事情在浏览器中也会以完全相同的方式工作。
@Gaurang Patel - 浏览器 javascript 引擎是否曾经退出? (我认为不是。)【参考方案2】:
我调试了几个小时,为什么我们的一个脚本在 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' 如果您使用第二个函数,则不会打印,如果您使用第一个函数,则会打印
【讨论】:
第二个,如果config.port
未定义,那么你的承诺永远不会解决,永远......因为你没有调用解决或拒绝。回调 (reject, resolve)=>
应该是同步的 - 如果你在那里提出一个套接字请求,它应该让节点保持活动状态。【参考方案3】:
作为一般说明,您的代码结合了三种相似但不同的方法:async/await、promises、事件侦听器。我不确定你所说的“轰炸”是什么意思。但是查看代码,结果似乎是预期的。
您的进程退出,因为您在添加事件侦听器时调用了 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))
【讨论】:
您的代码在语义上与 OP 的不同;您的 Promise 正在解决,因为您尚未将resolve
调用放入函数中,因此正在立即对其进行评估。
就是这个想法。他对它为什么会这样工作感到困惑,所以我写了另一种方式来展示它。除了理解,没有什么可以“解决”的。
恐怕它永远不会 resolve 因为事件永远不会发出。该示例是人为设计的,最终归结为 Mark 的回答。
我明白这一点。我试图表明,如果没有 emit 或 setTimeout,则解析(甚至嵌套)不会阻止您的程序停止。但无论如何,马克的回答更清楚。
好吧,我的例子是人为的(没有尝试)-我实际上是在做流->异步可迭代转换(这就是我混合这三个的原因)。我猜你说的是当你说“resolved”时返回的函数,但在promise的上下文中,“resolve”是promise完成的时候。这就是为什么你的答案乍一看似乎是错误的。以上是关于节点无错误退出且不等待承诺(事件回调)的主要内容,如果未能解决你的问题,请参考以下文章