【问题标题】:How to prevent Node.js from exiting while waiting for a callback?如何防止 Node.js 在等待回调时退出?
【发布时间】:2011-09-20 12:34:15
【问题描述】:

我有这样的代码:

var client = new mysql.Client(options);
console.log('Icanhasclient');

client.connect(function (err) {
  console.log('jannn');
  active_db = client;
  console.log(err);
  console.log('hest');

  if (callback) {
    if (err) {
      callback(err, null);
    }

    callback(null, active_db);
  }
});

我的问题是 Node 在我运行时立即终止。它打印“Icanhasclient”,但回调中的任何 console.log 都没有被调用。

(本例中的mysql是node-mysql

有什么办法可以让 node.js 在退出之前等待回调完成?

【问题讨论】:

  • 阻止退出是什么意思?在所有回调完成之前,Nodejs 不会退出。这是一个单线程进程。
  • @nEEbz:如果是这样,为什么我的脚本没有执行回调就退出了?
  • 连接数据库有问题;我不太清楚为什么它没有触发回调。有没有报错?
  • 不,它只是默默地失败:(
  • 这很奇怪。我希望我们能找到答案。

标签: node.js


【解决方案1】:

这就是我的做法。我让我的主入口点返回一个承诺,然后我使用这个小包装器来确保只要该承诺没有解决,节点就不会退出:

function wrapPromiseMain(entryPoint) {
    const pollTime = 1000000;
    let interval = setInterval(() => {}, pollTime);
    
    return entryPoint().finally(() => {clearInterval(interval)});
}

要使用它,只需承诺化您的主入口点并将其作为参数传递给包装器:

function main() {

    // ... main stuff ...

    return doSomethingAsync();
}

wrapPromiseMain(main);

我发现它比基本的轮询循环更简洁,因为它会在 promise 完成时自动取消计时器,因此不会增加任何额外的延迟。因此,如果您愿意,轮询时间基本上可以是永久的。

【讨论】:

  • 注意:在发布后进行了编辑,因为我意识到我可以通过使用 setInterval 而不是对 setTimeout 的链式调用来使其更加简洁。
【解决方案2】:

请试试这个。检查这是否有帮助。

var client = new mysql.Client(options);
console.log('Icanhasclient');
var verbose;

if (!verbose) {
    return new Promise(function (resolve, reject) {
        client.connect(function (err) {
            if (err) {
                console.log(Error in connecting
                SQL ${err}
            )
                ;
                return reject(err);
            }
            verbose = client;
            return resolve(verbose);
        })
    })
} else {
    return new Promise(function (resolve) {
        resolve(verbose);
    })
}

【讨论】:

    【解决方案3】:

    这是我的两分钱:

    async function main()
    {
        await new Promise(function () {});
        console.log('This text will never be printed');
    }
    
    function panic(error)
    {
        console.error(error);
        process.exit(1);
    }
    
    // https://stackoverflow.com/a/46916601/1478566
    main().catch(panic).finally(clearInterval.bind(null, setInterval(a=>a, 1E9)));
    

    【讨论】:

      【解决方案4】:

      根据@Todd 的回答,我创建了一个单行。将其包含在脚本的开头,完成后设置done = true

      var done = (function wait () { if (!done) setTimeout(wait, 1000) })();
      

      例子:

      var done = (function wait () { if (!done) setTimeout(wait, 1000) })();
      
      someAsyncOperation().then(() => {
        console.log('Good to go!');
        done = true;
      });
      

      它是如何工作的?如果我们稍微扩展一下:

      // Initialize the variable `done` to `undefined`
      // Create the function wait, which is available inside itself
      // Note: `var` is hoisted but `let` is not so we need to use `var`
      var done = (function wait () {
      
        // As long as it's nor marked as done, create a new event+queue
        if (!done) setTimeout(wait, 1000);
      
        // No return value; done will resolve to false (undefined)
      })();
      

      【讨论】:

      • 你为什么要在 IIFE 的输出中设置 done,在这之后使用 IIFE 最好是 false。
      • @tiffon 可能只是为了使它成为一个单线。
      【解决方案5】:

      我的解决方案是实例化一个 EventEmitter,并监听我的自定义事件。

      var eventEmitter = new process.EventEmitter();
      

      然后我从异步回调中调用了eventEmitter.emit

      client.connect(function (err) {
          eventEmitter.emit('myevent', {something: "Bla"})
      });
      

      我脚本中的最后一件事是eventEmitter.on

      eventEmitter.on('myevent', function(myResult){
        // I needed the result to be written to stdout so that the calling process could get it
        process.stdout.write(JSON.stringify(myResult));
      });
      

      然后节点将等待事件处理程序完成运行。

      【讨论】:

      • 我收到`process.EventEmitter is not a constructor`错误。你有什么建议?
      • 查看如何创建 EventEmitter。 coligo.io/nodejs-event-emitter 似乎是一个很好的资源。可能是 Node 的不同版本改变了创建方式。
      • EventEmitter 类由events 模块定义。所以,要创建一个新的发射器:const EventEmitter = require('events'); var eventEmitter = new EventEmitter().
      • 警告 - 查看我对已接受答案的评论。我认为这是错误的,至少对于当前版本的节点不起作用。
      【解决方案6】:

      您可以使用 setInterval 发出 setTimeout 或重复超时。

      如果你想检查退出条件,你也可以做一个条件超时:

      (function wait () {
         if (!SOME_EXIT_CONDITION) setTimeout(wait, 1000);
      })();
      

      把它放在你代码的最后,控制台会一直等待……然后等待……直到你想要它关闭。

      【讨论】:

      • 这是一个很好的解决方法,但不是一个很好的解决方案(不是你的错,而是节点的错)。
      【解决方案7】:

      我确实查看了felixge/node-mysql 库,但没有在 API 中看到对命令 client.connect 的引用。这是您要拨打的实际电话(不是在这里挑剔)吗?无论如何,恕我直言,您需要更多地考虑 Javascript 的设计方式,因为它使用的编程范式不同于大多数其他流行语言。

      我在您的代码中看到的第一个问题是您尚未定义回调,因此它实际上并不存在。我假设 console.log(callback) 是未定义的。从您的代码中,匿名函数是 client.connect 函数的“回调”。您必须在更高的范围内定义您所谓的“回调”。例如,我将定义一个函数 myCallback 存在于高于 client.connect 的匿名函数的范围内。查找 Javascript variable scope 可能很有用。

          var myCallback(err, response) {
            if (err) {
              console.log('err:%s',err);
            } else {
              console.log('response:%s',response);
            }
          }
      
          client.connect(err, function(response) {
            // this anonymous function is the callback to client.connect, the var
            // 'callback' would be undefined.
            if (err) {
              myCallback(err);
              return; // Explicit call to return, else the lines below would run.
            } 
            myCallback(null, response);
          });
      

      其次,如果你没有在Javascript中显式调用return,函数会继续处理。我被这个myself 咬了。最后,Javascript 运行一个event-driven 循环,这意味着它永远不会等待函数返回一个值,这就是我们首先拥有所有这些回调的原因。您可以强制 Javascript 表现不同,例如通过使用 while 循环直到条件为真。请参阅caolan 的“异步”库,了解操作事件循环的各种策略。过度使用这些方法的主要缺点是您实际上浪费了 CPU 周期/阻塞,而您可能应该使用更多回调并简单地重新考虑您的程序是如何工作的。

      【讨论】:

        【解决方案8】:

        回调未排队

        节点运行直到所有事件队列为空。当调用诸如

        时,将回调添加到事件 queue
          emmiter1.on('this_event',callback).
        

        已执行。此调用是模块开发人员编写的代码的一部分。

        如果一个模块是同步/阻塞版本的快速端口,这可能不会发生,直到操作的某些部分完成并且所有队列可能在此之前清空,从而允许节点静默退出。

        这是一个鬼鬼祟祟的错误,模块开发人员在开发过程中可能不会遇到,因为它在具有许多 队列 的繁忙系统中发生的频率较低,因为对于所有它们在关键时刻为空。

        用户可能的修复/错误检测器是在可疑函数调用之前插入一个特殊的计时器事件。

        【讨论】:

        • “在某些操作完成之前,这可能不会发生” - 你能澄清一下吗?您是说节点可能会在一段代码仍在运行之前退出,然后才有机会向事件队列添加回调?
        • 我猜他的意思是,如果库已将曾经同步的代码转换为新的异步版本,则库创建者可能忘记将回调排队,并且事件队列在中间某处被清空你的执行,从而以未调用的回调结束。
        • 有没有简单的方法可以可靠地重现这个问题?
        • 谢谢!我有一个“挂起”的过程,不明白为什么它不会退出。清理完我所有的监听器/事件回调后,进程退出了。
        • 警告 - 尽管有很多赞成票,但我认为此答案中的信息不正确。调用EventEmitter.on() 确实不会将任何内容添加到Node 将等待的事件循环中(至少在当前版本的Node 中)。似乎只有像设置超时和从核心库进行异步调用这样的事情才能做到这一点。您可以通过编写一个预期永远不会触发的事件的单行程序来轻松测试这一点。从这个答案中,您希望它永远不会终止,但它确实会立即终止。 See this answer 了解更多信息。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-03
        • 2016-02-17
        • 1970-01-01
        • 2010-10-09
        • 2023-04-06
        相关资源
        最近更新 更多