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

我想告诉Node.js总是在它退出之前做一些事情,不管是出于什么原因——Ctrl+C,一个异常,或任何其他原因。

我试了一下:

process.on('exit', function (){
console.log('Goodbye!');
});

我启动了这个程序,扼杀了它,然后什么都没发生。我再次启动它,按Ctrl+C,仍然没有任何反应…

240954 次浏览

更新:

你可以为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}));

"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.');
});

注意:这似乎会中断sigint,当你完成代码时,你需要调用process.exit()。

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

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);
});
};

这段代码拦截未捕获的异常、Ctrl+C和正常退出事件。然后,它在退出前调用一个可选的用户清理回调函数,用一个对象处理所有退出条件。

该模块只是扩展了流程对象,而不是定义另一个事件发射器。如果没有应用程序特定的回调,清理默认为no op函数。这对于我的使用是足够的,当通过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

io.js有一个exit和一个beforeExit事件,它们做你想做的事情。

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

在进程由另一个节点进程生成的情况下,例如:

var child = spawn('gulp', ['watch'], {
stdio: 'inherit',
});

然后你试图通过以下方式杀死它:

child.kill();

这是如何处理[子]事件的:

process.on('SIGTERM', function() {
console.log('Goodbye!');
});

只是想在这里提到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
})

这将捕获我能找到的可以处理的每个退出事件。目前看来很可靠,也很干净。

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

这里有一个针对windows的好方法

process.on('exit', async () => {
require('fs').writeFileSync('./tmp.js', 'crash', 'utf-8')
});

在尝试了其他答案之后,下面是我对这个任务的解决方案。实现这种方式可以帮助我将清理工作集中在一个地方,防止重复处理清理工作。

  1. 我想将所有其他退出代码路由到“退出”代码。
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
process.on(eventType, exitRouter.bind(null, { exit: true }));
})
  1. exitRouter所做的是调用process.exit()
function exitRouter(options, exitCode) {
if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
if (options.exit) process.exit();
}
  1. 在'exit'上,使用一个新函数处理清理
function exitHandler(exitCode) {
console.log(`ExitCode ${exitCode}`);
console.log('Exiting finally...')
}


process.on('exit', exitHandler)

为了演示的目的,这是link到我的要点。在文件中,我添加了一个setTimeout来伪造进程运行。

如果你运行node node-exit-demo.js并且什么都不做,那么在2秒后,你会看到日志:

The service is finish after a while.
ExitCode 0
Exiting finally...

否则,如果在服务结束之前,你通过ctrl+C终止,你会看到:

^CExitCode SIGINT
ExitCode 0
Exiting finally...

发生的情况是Node进程最初以代码SIGINT退出,然后路由到process.exit(),最后以退出代码0退出。

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

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

所以我自己试了一下,最后发现了这个:

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


process.exit(0)
})


process.once('SIGINT', () => { throw new Error() })