441

I want to tell Node.js to always do something just before it exits, for whatever reason — Ctrl+C, an exception, or any other reason.

I tried this:

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

I started the process, killed it, and nothing happened. I started it again, pressed Ctrl+C, and still nothing happened...

Erel Segal-Halevi
  • 33,955
  • 36
  • 114
  • 183

13 Answers13

680

UPDATE:

You can register a handler for `process.on('exit')` and in any other case(`SIGINT` or unhandled exception) to call `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}));

This only works if you call synchronous code inside the handler, otherwise it will call the handler indefinitely

Luis Gonzalez
  • 531
  • 5
  • 16
Emil Condrea
  • 9,705
  • 7
  • 33
  • 52
  • 6
    Is there a way to handle both Ctrl+C and a usual exit in the same place, or do I have to write two separate handlers? What about other types of exit, such as unhandled exception - there is a specific handler for that case, but should I handle this with a third copy of the same handler? – Erel Segal-Halevi Dec 26 '12 at 09:04
  • On linux it works with SIGINT event, so you can actually catch the CTRL+C action, but windows version of nodejs does not have direct suport for this. For unhandled exceptions you can catch it with : http://nodejs.org/api/process.html#process_event_uncaughtexception – Emil Condrea Dec 26 '12 at 12:46
  • 1
    @RobFox resume() initializes reading process. Stdin is paused by default. You can read more on : https://github.com/joyent/node/blob/544e73b4b80f577424ab7370e6f72f8d65b98674/doc/api/process.markdown#processstdin – Emil Condrea Feb 05 '14 at 12:08
  • @ErelSegalHalevi, you can simply have a named function, then register that function as the handler for all of the events... using .bind, you can even prefix which handler you're conditioned for to the arguments. – Tracker1 Feb 17 '14 at 20:19
  • @DeaDEnD what does not work? on which version of node? – Emil Condrea Mar 01 '14 at 05:19
  • Well, it works. It just doesn't look like the Terminal app is prevented from being detached from the program, but the program does still execute the `exitHandler()`. I'm on node v0.10.26 and OSX 10.9.1. – fent Mar 01 '14 at 16:27
  • hm. So you want to prevent the application from being closed? Just delete the above line where is called `process.exit();` – Emil Condrea Mar 01 '14 at 17:43
  • 101
    Note that you `must only` perform `synchronous` operations in `exit` handler – Lewis Feb 10 '15 at 14:23
  • yeah! this SO does not help to deal with async.. i m about to do that but feel like it s not right `process.on('exit', function(code) { server.close(); });` server being http one, close expecting an optional callback.... –  Nov 19 '15 at 14:27
  • So I can't log to my mongodb with winston on a failure because it would be async inside the exit handler? – Andi Giga Jan 22 '16 at 15:22
  • 1
    @EmilCondrea what is that "null" as first parameter for? That function works fine to me but I'd like to understand that more. Why we need to bind instead of just call the function like exitHandler(null, {exit: true})? I noticed that without .bind, the function is called automatically after starting server. Why? Thank you – elzoy Oct 20 '16 at 19:21
  • 1
    @Tresdin Refering to your comment saying you can only perform synchronous actions, is there any way to perform asynchronous actions on the `exit` handler? – Kesem David Nov 10 '16 at 13:24
  • 4
    @KesemDavid I think you should use the [`beforeExit`](https://nodejs.org/api/process.html#process_event_beforeexit) event instead. – Lewis Nov 10 '16 at 14:19
  • @Tresdin is this available on node v6.9? I can't seem to be able to use this event – Kesem David Nov 10 '16 at 16:37
  • 40
    This solution has numerous issues. (1) It doesn't report signals to parent processes. (2) It doesn't convey the exit code to the parent process. (3) It doesn't allow for Emacs-like children that ignore Ctrl-C SIGINT. (4) It doesn't allow asynchronous cleanup. (5) It doesn't coordinate a single `stderr` message across multiple cleanup handlers. I've written a module that does all this, https://github.com/jtlapp/node-cleanup, originally based on the cleanup.js solution below, but greatly revised based on feedback. I hope it proves helpful. – Joe Lapp Dec 27 '16 at 05:45
  • * It's possible to perform asynchronous cleanup for POSIX signals other than SIGKILL. – Joe Lapp Dec 27 '16 at 05:53
  • 3
    You must not call `process.exit` inside `on("exit", ...)` handler. This handler IS meant to be called after `process.exit` call https://nodejs.org/api/process.html#process_event_exit Consequences will be very bad, for example, all errors will be muted. – Ivan Kleshnin Jan 12 '18 at 17:47
  • If you exit on `uncaughtException`, then it is also advisable to react on `unhandledRejection`. – Benny Code Oct 08 '19 at 09:57
  • @JoeLapp A user is debugging a node.js script in VsCode. Is it possible to catch `process.exit` inside of this application script when user clicks "stop debugging" in the IDE before the script is finished or hits `ctrl+c` ? – Legends Jan 11 '20 at 01:27
  • This solution's really dangerous for the fact that if an uncaught exception occurs, the program exits without even logging what happened. – Alessandro Iudicone May 07 '21 at 09:36
  • 1
    @JoeLapp Thank you for your contributions, they're truly appreciated! – aggregate1166877 Nov 12 '22 at 09:15
205

The script below allows having a single handler for all exit conditions. It uses an app specific callback function to perform custom cleanup code.

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

This code intercepts uncaught exceptions, Ctrl+C and normal exit events. It then calls a single optional user cleanup callback function before exiting, handling all exit conditions with a single object.

The module simply extends the process object instead of defining another event emitter. Without an app specific callback the cleanup defaults to a no op function. This was sufficient for my use where child processes were left running when exiting by Ctrl+C.

You can easily add other exit events such as SIGHUP as desired. Note: per NodeJS manual, SIGKILL cannot have a listener. The test code below demonstrates various ways of using 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 
Josh Correia
  • 3,807
  • 3
  • 33
  • 50
CanyonCasa
  • 2,051
  • 1
  • 11
  • 2
  • @Pier-LucGendreau where does this specific code go? – hownowbrowncow Mar 28 '16 at 20:04
  • 18
    I've found this code indispensable and created a node package for it, with modifications, crediting you and this SO answer. Hope that's okay, @CanyonCasa. Thank you! https://www.npmjs.com/package/node-cleanup – Joe Lapp Sep 13 '16 at 16:07
  • 4
    I love the cleanup. But I don't like the process.exit(0); https://www.cons.org/cracauer/sigint.html My feeling is you should let the kernel handle the destruction. You are not exiting the same way that a SIGINT. SIGINT doesn't exit with 2. You are mistaking the SIGINT with error code. They aren't the same. Actually Ctrl+C exist with 130. Not 2. http://www.tldp.org/LDP/abs/html/exitcodes.html – Banjocat Nov 18 '16 at 02:44
  • 8
    I have rewritten https://www.npmjs.com/package/node-cleanup so that SIGINT handling works well with other processes, per @Banjocat's link. It also now correctly relays signals to the parent process instead of calling `process.exit()`. Cleanup handlers now have the flexibility to behave as a function of exit code or signal, and cleanup handlers can be uninstalled, as necessary to support asynchronous cleanup or prevent cyclic cleanup. It now has little resemblance to the above code. – Joe Lapp Dec 27 '16 at 05:28
  • 4
    Forgot to mention that I also made a (hopefully) comprehensive test suite. – Joe Lapp Dec 27 '16 at 05:36
  • I had to add `SIGTERM` to get it to work with systemd. – Akkumulator Dec 09 '17 at 13:21
  • Yes, you need `SIGTERM`. Much simpler to just do a forEach on this list of events: [`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`] – light24bulbs Mar 20 '18 at 19:31
  • 1
    Not sure if you can safely do `process.emit('cleanup');` in the exit callback. nodejs docs state that "Listener functions must only perform synchronous operations. The Node.js process will exit immediately after calling the 'exit' event listeners causing any additional work still queued in the event loop to be abandoned" – aromero May 28 '19 at 22:33
77

This catches every exit event I can find that can be handled. Seems quite reliable and clean so far.

[`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`].forEach((eventType) => {
  process.on(eventType, cleanUpServer.bind(null, eventType));
})
light24bulbs
  • 2,871
  • 3
  • 24
  • 33
24

"exit" is an event that gets triggered when node finish it's event loop internally, it's not triggered when you terminate the process externally.

What you're looking for is executing something on a SIGINT.

The docs at http://nodejs.org/api/process.html#process_signal_events give an example:

Example of listening for 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.');
});

Note: this seems to interrupt the sigint and you would need to call process.exit() when you finish with your code.

user1278519
  • 861
  • 6
  • 9
  • 2
    Is there a way to handle both Ctrl+C and a usual exit in the same place? Or do I have to write two identical handlers? – Erel Segal-Halevi Dec 25 '12 at 20:49
  • Just as a note, if you have to end node by a kill command doing `kill -2` will pass the `SIGINT` code. We have to do it this way because we have node logging to a txt file so Ctrl + C is not possible. – Aust Feb 14 '13 at 23:22
13
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
Bombel
  • 159
  • 2
  • 9
Abdullah
  • 968
  • 12
  • 17
  • 7
    This answer deserves all the glory, but because there is no explanation, there is no up-vote either unfortunately. What *significant* about this answer is, [the doc says](https://nodejs.org/api/process.html#process_event_exit), _"Listener functions must only perform synchronous operations. The Node.js process will exit immediately after calling the 'exit' event listeners causing any additional work still queued in the event loop to be abandoned. "_, and this answer overcomes that limitation! – xpt Feb 25 '18 at 14:36
  • If I am reading this correctly, exiting will always take a fixed amount of time (five seconds), which may be too long or worse, too short, if the asynchronous operation needs more time. – Sebastian Dec 20 '20 at 11:32
9

Just wanted to mention death package here: https://github.com/jprichardson/node-death

Example:

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

ON_DEATH(function(signal, err) {
  //clean up code here
})
gorodezkiy
  • 3,299
  • 2
  • 34
  • 42
8

async-exit-hook seems to be the most up-to-date solution for handling this problem. It's a forked/re-written version of exit-hook that supports async code before exiting.

Cory Danielson
  • 14,314
  • 3
  • 44
  • 51
5

I need to do an asynchronous cleanup action on exit, none of the answers in this question worked for me.

So I tried it myself, and finally found this:

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

  process.exit(0)
})

process.once('SIGINT', () => { throw new Error() })
BlackGlory
  • 3,866
  • 2
  • 15
  • 21
1

After playing around with other answer, here is my solution for this task. Implementing this way helps me centralize cleanup in one place, preventing double handling the cleanup.

  1. I would like to route all other exiting codes to 'exit' code.
const others = [`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`]
others.forEach((eventType) => {
    process.on(eventType, exitRouter.bind(null, { exit: true }));
})
  1. What the exitRouter does is calling process.exit()
function exitRouter(options, exitCode) {
   if (exitCode || exitCode === 0) console.log(`ExitCode ${exitCode}`);
   if (options.exit) process.exit();
}
  1. On 'exit', handle the clean up with a new function
function exitHandler(exitCode) {
  console.log(`ExitCode ${exitCode}`);
  console.log('Exiting finally...')
}

process.on('exit', exitHandler)

For the demo purpose, this is link to my gist. In the file, i add a setTimeout to fake the process running.

If you run node node-exit-demo.js and do nothing, then after 2 seconds, you see the log:

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

Else if before the service finish, you terminate by ctrl+C, you'll see:

^CExitCode SIGINT
ExitCode 0
Exiting finally...

What happened is the Node process exited initially with code SIGINT, then it routes to process.exit() and finally exited with exit code 0.

Le Hieu
  • 11
  • 1
  • 2
0

io.js has an exit and a beforeExit event, which do what you want.

Golo Roden
  • 140,679
  • 96
  • 298
  • 425
0

To those of you running npm scripts that would like to perform an action before the process finishes through the script line definition without extra file definition you can just add a semicolon (checked on mac)

for example:

"start": "npm run add-hosts-to-hosts-file && npm run start ; npm run clear-hosts-from-hosts file",

this covers the scenario for Ctrl + C. Might also cover other cases.

Noy Oliel
  • 1,430
  • 3
  • 13
  • 26
-1

In the case where the process was spawned by another node process, like:

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

And you try to kill it later, via:

child.kill();

This is how you handle the event [on the child]:

process.on('SIGTERM', function() {
    console.log('Goodbye!');
});
Jaime Gómez
  • 6,961
  • 3
  • 40
  • 41
-4

Here's a nice hack for windows

process.on('exit', async () => {
    require('fs').writeFileSync('./tmp.js', 'crash', 'utf-8')
});
  • Not really a nice hack if you fail to supply some elaboration of what your code does and what goal it achieves. I can only guess that's why you got the downvotes. – oligofren Jan 31 '22 at 18:52