【发布时间】:2012-12-11 11:46:39
【问题描述】:
我想告诉 Node.js 总是在它退出之前做一些事情,无论出于何种原因 - Ctrl+C、异常或任何其他原因。
我试过这个:
process.on('exit', function (){
console.log('Goodbye!');
});
我启动了这个过程,然后终止了它,但什么也没发生。我又开始了,按了Ctrl+C,还是什么都没发生……
【问题讨论】:
标签: node.js
我想告诉 Node.js 总是在它退出之前做一些事情,无论出于何种原因 - Ctrl+C、异常或任何其他原因。
我试过这个:
process.on('exit', function (){
console.log('Goodbye!');
});
我启动了这个过程,然后终止了它,但什么也没发生。我又开始了,按了Ctrl+C,还是什么都没发生……
【问题讨论】:
标签: node.js
您可以为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}));
【讨论】:
must only在exit处理程序中执行synchronous操作
beforeExit 事件。
stderr 消息。我写了一个模块来完成这一切,github.com/jtlapp/node-cleanup,最初基于下面的 cleanup.js 解决方案,但根据反馈进行了很大的修改。我希望它会有所帮助。
on("exit", ...)处理程序中调用process.exit。这个处理程序应该在process.exit 调用nodejs.org/api/process.html#process_event_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 和正常退出事件。然后它在退出之前调用一个可选的用户清理回调函数,使用单个对象处理所有退出条件。
该模块只是扩展了流程对象,而不是定义另一个事件发射器。如果没有特定于应用程序的回调,则清理默认为无操作函数。这足以让我在通过 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
【讨论】:
process.exit()。清理处理程序现在可以灵活地作为退出代码或信号的函数运行,并且可以卸载清理处理程序,以支持异步清理或防止循环清理。它现在与上面的代码几乎没有相似之处。
process.emit('cleanup');。 nodejs 文档声明“侦听器函数只能执行同步操作。Node.js 进程将在调用 'exit' 事件侦听器后立即退出,从而导致事件循环中仍在排队的任何其他工作被放弃”
这会捕获我能找到的每个可以处理的退出事件。到目前为止看起来相当可靠和干净。
[`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`].forEach((eventType) => {
process.on(eventType, cleanUpServer.bind(null, eventType));
})
【讨论】:
man kill
“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()。
【讨论】:
kill -2 将传递 SIGINT 代码。我们必须这样做,因为我们有节点记录到一个 txt 文件,所以 Ctrl + C 是不可能的。
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
【讨论】:
只想在此处提及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
})
【讨论】:
async-exit-hook 似乎是处理此问题的最新解决方案。它是 exit-hook 的分叉/重写版本,支持退出前的异步代码。
【讨论】:
我需要在退出时执行异步清理操作,这个问题中的答案都对我不起作用。
于是我自己试了一下,终于找到了这个:
process.once('uncaughtException', async () => {
await cleanup()
process.exit(0)
})
process.once('SIGINT', () => { throw new Error() })
【讨论】:
在玩弄了其他答案之后,这是我针对此任务的解决方案。实施这种方式有助于我将清理工作集中在一个地方,避免重复处理清理工作。
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
process.on(eventType, exitRouter.bind(null, { exit: true }));
})
function exitRouter(options, exitCode) {
if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
if (options.exit) process.exit();
}
function exitHandler(exitCode) {
console.log(`ExitCode ${exitCode}`);
console.log('Exiting finally...')
}
process.on('exit', exitHandler)
出于演示目的,this is 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 退出。
【讨论】:
io.js 有一个 exit 和一个 beforeExit 事件,可以满足您的需求。
【讨论】:
如果该进程是由另一个节点进程产生的,例如:
var child = spawn('gulp', ['watch'], {
stdio: 'inherit',
});
然后你试图通过以下方式杀死它:
child.kill();
这就是您处理事件的方式 [on the child]:
process.on('SIGTERM', function() {
console.log('Goodbye!');
});
【讨论】:
这是一个不错的 windows hack
process.on('exit', async () => {
require('fs').writeFileSync('./tmp.js', 'crash', 'utf-8')
});
【讨论】: