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

时间:2017-10-24 15:07:46

标签: javascript node.js promise

我有一个非常奇怪的问题,等待一个已经通过其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进程正常终止。

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

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

3 个答案:

答案 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' 不会在你使用第二个函数时被打印出来,如果你使用第一个函数则会被打印