在node.js退出之前执行清理操作

Ere*_*evi 296 node.js

我想告诉node.js总是在它退出之前做某事,无论出于何种原因 - Ctrl + C,异常或任何其他原因.

我试过这个:

process.on('exit', function (){
  console.log('Goodbye!');
});
Run Code Online (Sandbox Code Playgroud)

开始这个过程,杀了它,什么都没发生; 再次启动,按下Ctrl + C,仍然没有发生任何事......

Emi*_*rea 469

更新:

您可以为process.on('exit')任何其他情况(SIGINT或未处理的异常)注册处理程序以进行调用process.exit()

process.stdin.resume();//so the program will not close instantly

function exitHandler(options, exitCode) {
    if (options.cleanup) console.log('clean');
    if (exitCode || exitCode === 0) console.log(exitCode);
    if (options.exit) process.exit();
}

//do something when app is closing
process.on('exit', exitHandler.bind(null,{cleanup:true}));

//catches ctrl+c event
process.on('SIGINT', exitHandler.bind(null, {exit:true}));

// catches "kill pid" (for example: nodemon restart)
process.on('SIGUSR1', exitHandler.bind(null, {exit:true}));
process.on('SIGUSR2', exitHandler.bind(null, {exit:true}));

//catches uncaught exceptions
process.on('uncaughtException', exitHandler.bind(null, {exit:true}));
Run Code Online (Sandbox Code Playgroud)

  • 请注意,您只能在`exit`处理程序中执行`synchronous`操作 (54认同)
  • 该解决方案存在许多问题.(1)它不向父进程报告信号.(2)它不会将退出代码传递给父进程.(3)它不允许类似Emacs的孩子忽略Ctrl-C SIGINT.(4)它不允许异步清理.(5)它不会跨多个清理处理程序协调单个`stderr`消息.我编写了一个完成所有这些操作的模块,https://github.com/jtlapp/node-cleanup,最初基于下面的cleanup.js解决方案,但根据反馈进行了大幅修改.我希望它证明是有帮助的. (17认同)
  • 你能解释一下process.stdin.resume(),它是如何工作的? (9认同)
  • 有没有办法在同一个地方处理Ctrl + C和通常的退出,或者我是否必须编写两个单独的处理程序?那么其他类型的退出呢,比如未处理的异常 - 这种情况有一个特定的处理程序,但是我应该用同一个处理程序的第三个副本来处理它吗? (3认同)
  • 你不能在 `on("exit", ...)` 处理程序中调用 `process.exit`。这个处理程序是在 `process.exit` 调用之后调用 https://nodejs.org/api/process.html#process_event_exit 后果会很糟糕,例如,所有错误都将被静音。 (3认同)
  • @KesemDavid我认为你应该使用[`beforeExit`](https://nodejs.org/api/process.html#process_event_beforeexit)事件. (2认同)

小智 173

下面的脚本允许为所有退出条件设置单个处理程序.它使用特定于应用程序的回调函数来执行自定义清理代码.

cleanup.js

// Object to capture process exits and call app specific cleanup function

function noOp() {};

exports.Cleanup = function Cleanup(callback) {

  // attach user callback to the process event emitter
  // if no callback, it will still exit gracefully on Ctrl-C
  callback = callback || noOp;
  process.on('cleanup',callback);

  // do app specific cleaning before exiting
  process.on('exit', function () {
    process.emit('cleanup');
  });

  // catch ctrl+c event and exit normally
  process.on('SIGINT', function () {
    console.log('Ctrl-C...');
    process.exit(2);
  });

  //catch uncaught exceptions, trace, then exit normally
  process.on('uncaughtException', function(e) {
    console.log('Uncaught Exception...');
    console.log(e.stack);
    process.exit(99);
  });
};
Run Code Online (Sandbox Code Playgroud)

此代码拦截未捕获的异常,Ctrl-C和正常退出事件.然后在退出之前调用单个可选的用户清理回调函数,使用单个对象处理所有退出条件.

该模块只是扩展了过程对象,而不是定义另一个事件发射器.如果没有特定于应用程序的回调,则清除默认为无操作.这对于我在使用Ctrl-C退出时保持子进程运行的用途已足够.

您可以根据需要轻松添加其他退出事件,例如SIGHUP.注意:根据NodeJS手册,SIGKILL不能拥有监听器.下面的测试代码演示了使用cleanup.js的各种方法

// test cleanup.js on version 0.10.21

// loads module and registers app specific cleanup callback...
var cleanup = require('./cleanup').Cleanup(myCleanup);
//var cleanup = require('./cleanup').Cleanup(); // will call noOp

// defines app specific callback...
function myCleanup() {
  console.log('App specific cleanup code...');
};

// All of the following code is only needed for test demo

// Prevents the program from closing instantly
process.stdin.resume();

// Emits an uncaught exception when called because module does not exist
function error() {
  console.log('error');
  var x = require('');
};

// Try each of the following one at a time:

// Uncomment the next line to test exiting on an uncaught exception
//setTimeout(error,2000);

// Uncomment the next line to test exiting normally
//setTimeout(function(){process.exit(3)}, 2000);

// Type Ctrl-C to test forced exit 
Run Code Online (Sandbox Code Playgroud)

  • 这个答案应该得到更多的信任. (22认同)
  • 我发现这段代码是必不可少的,并为它创建了一个节点包,并进行了修改,归功于您和这个SO答案.希望没关系,@ CamanyCasa.谢谢!https://www.npmjs.com/package/node-cleanup (10认同)
  • 我已经重写了https://www.npmjs.com/package/node-cleanup,以便根据@ Banjocat的链接,SIGINT处理与其他进程配合良好.它现在也正确地将信号中继到父进程,而不是调用`process.exit()`.清理处理程序现在可以灵活地作为退出代码或信号的函数,并且可以卸载清理处理程序,以支持异步清理或防止循环清理.它现在与上面的代码几乎没有相似之处. (5认同)
  • 我喜欢清理.但我不喜欢process.exit(0); https://www.cons.org/cracauer/sigint.html我的感觉是你应该让内核处理破坏.您没有退出与SIGINT相同的方式.SIGINT不会以2退出.您错误地将SIGINT与错误代码混淆.他们不一样.实际上Ctrl + C与130存在.不是2. http://www.tldp.org/LDP/abs/html/exitcodes.html (3认同)
  • 忘记提到我也做了一个(希望)全面的测试套件. (3认同)

use*_*519 20

"exit"是一个事件,当节点在内部完成它的事件循环时触发,当你从外部终止进程时它不会被触发.

您正在寻找的是在SIGINT上执行某些操作.

http://nodejs.org/api/process.html#process_signal_events上的文档举例说明:

侦听SIGINT的示例:

// Start reading from stdin so we don't exit.
process.stdin.resume();

process.on('SIGINT', function () {
  console.log('Got SIGINT.  Press Control-D to exit.');
});
Run Code Online (Sandbox Code Playgroud)

注意:这似乎会中断sigint,您需要在完成代码后调用process.exit().

  • 有没有办法在同一个地方同时处理 Ctrl+C 和通常的退出?或者我必须编写两个相同的处理程序? (2认同)

lig*_*lbs 19

这可以捕获我可以找到的每个可以处理的退出事件.到目前为止看起来非常可靠和干净.

[`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`].forEach((eventType) => {
  process.on(eventType, cleanUpServer.bind(null, eventType));
})
Run Code Online (Sandbox Code Playgroud)

  • 这太棒了! (3认同)
  • 只需记住 - 在你的清理函数中 - 最后调用“process.exit()”。原因是,您的函数将覆盖默认行为,例如终止端口(请参阅节点文档:https://nodejs.org/api/process.html#process_signal_events) (3认同)

Abd*_*dın 8

function fnAsyncTest(callback) {
    require('fs').writeFile('async.txt', 'bye!', callback);
}

function fnSyncTest() {
    for (var i = 0; i < 10; i++) {}
}

function killProcess() {

    if (process.exitTimeoutId) {
        return;
    }

    process.exitTimeoutId = setTimeout(() => process.exit, 5000);
    console.log('process will exit in 5 seconds');

    fnAsyncTest(function() {
        console.log('async op. done', arguments);
    });

    if (!fnSyncTest()) {
        console.log('sync op. done');
    }
}

// https://nodejs.org/api/process.html#process_signal_events
process.on('SIGTERM', killProcess);
process.on('SIGINT', killProcess);

process.on('uncaughtException', function(e) {

    console.log('[uncaughtException] app will be terminated: ', e.stack);

    killProcess();
    /**
     * @https://nodejs.org/api/process.html#process_event_uncaughtexception
     *  
     * 'uncaughtException' should be used to perform synchronous cleanup before shutting down the process. 
     * It is not safe to resume normal operation after 'uncaughtException'. 
     * If you do use it, restart your application after every unhandled exception!
     * 
     * You have been warned.
     */
});

console.log('App is running...');
console.log('Try to press CTRL+C or SIGNAL the process with PID: ', process.pid);

process.stdin.resume();
// just for testing
Run Code Online (Sandbox Code Playgroud)

  • 这个答案值得所有的荣耀,但由于没有解释,不幸的是没有上升.关于这个答案的重要*,[文档说](https://nodejs.org/api/process.html#process_event_exit),_"监听器函数必须只执行同步操作.Node.js进程将立即退出在调用'exit'事件监听器之后,导致仍然在事件循环中排队的任何其他工作被放弃."_,这个答案克服了这个限制! (3认同)

ant*_*kiy 5

只是想在death这里提一下包:https://github.com/jprichardson/node-death

例:

var ON_DEATH = require('death')({uncaughtException: true}); //this is intentionally ugly

ON_DEATH(function(signal, err) {
  //clean up code here
})
Run Code Online (Sandbox Code Playgroud)


Cor*_*son 5

async-exit-hook似乎是处理这个问题的最新解决方案。它是退出钩子的分叉/重写版本,在退出之前支持异步代码。


Bla*_*ory 5

我需要在退出时执行异步清理操作,这个问题的答案都不适合我。

于是我自己尝试了一下,最后发现了这个:

process.once('uncaughtException', async () => {
  await cleanup()

  process.exit(0)
})

process.once('SIGINT', () => { throw new Error() })
Run Code Online (Sandbox Code Playgroud)