Socket
Socket
Sign inDemoInstall

exit-hook

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

exit-hook - npm Package Compare versions

Comparing version 2.2.1 to 4.0.0

77

index.d.ts
/**
@callback onExit
@param {number} signal - The exit code.
*/
/**
Run some code when the process exits.

@@ -6,5 +11,5 @@

This package is useful for cleaning up before exiting.
This is useful for cleaning synchronously before exiting.
@param callback - The callback to execute when the process exits.
@param {onExit} onExit - The callback function to execute when the process exits.
@returns A function that removes the hook when called.

@@ -14,6 +19,6 @@

```
import exitHook = require('exit-hook');
import exitHook from 'exit-hook';
exitHook(() => {
console.log('Exiting');
exitHook(signal => {
console.log(`Exiting with signal: ${signal}`);
});

@@ -37,4 +42,62 @@

*/
declare function exitHook(callback: () => void): () => void;
export default function exitHook(onExit: (signal: number) => void): () => void;
export = exitHook;
/**
Run code asynchronously when the process exits.
@see https://github.com/sindresorhus/exit-hook/blob/main/readme.md#asynchronous-exit-notes
@param {onExit} onExit - The callback function to execute when the process exits via `gracefulExit`, and will be wrapped in `Promise.resolve`.
@returns A function that removes the hook when called.
@example
```
import {asyncExitHook} from 'exit-hook';
asyncExitHook(() => {
console.log('Exiting');
}, {
wait: 500
});
throw new Error('🦄');
//=> 'Exiting'
// Removing an exit hook:
const unsubscribe = asyncExitHook(() => {}, {wait: 500});
unsubscribe();
```
*/
export function asyncExitHook(onExit: (signal: number) => (void | Promise<void>), options: Options): () => void;
/**
Exit the process and make a best-effort to complete all asynchronous hooks.
If you are using `asyncExitHook`, consider using `gracefulExit()` instead of `process.exit()` to ensure all asynchronous tasks are given an opportunity to run.
@param signal - The exit code to use. Same as the argument to `process.exit()`.
@see https://github.com/sindresorhus/exit-hook/blob/main/readme.md#asynchronous-exit-notes
@example
```
import {asyncExitHook, gracefulExit} from 'exit-hook';
asyncExitHook(() => {
console.log('Exiting');
}, {
wait: 500
});
// Instead of `process.exit()`
gracefulExit();
```
*/
export function gracefulExit(signal?: number): void;
export type Options = {
/**
The amount of time in milliseconds that the `onExit` function is expected to take. When multiple async handlers are registered, the longest `wait` time will be used.
*/
readonly wait: number;
};

@@ -1,8 +0,10 @@

'use strict';
import process from 'node:process';
const asyncCallbacks = new Set();
const callbacks = new Set();
let isCalled = false;
let isRegistered = false;
function exit(exit, signal) {
async function exit(shouldManuallyExit, isSynchronous, signal) {
if (isCalled) {

@@ -14,27 +16,76 @@ return;

if (asyncCallbacks.size > 0 && isSynchronous) {
console.error([
'SYNCHRONOUS TERMINATION NOTICE:',
'When explicitly exiting the process via process.exit or via a parent process,',
'asynchronous tasks in your exitHooks will not run. Either remove these tasks,',
'use gracefulExit() instead of process.exit(), or ensure your parent process',
'sends a SIGINT to the process running this code.',
].join(' '));
}
const exitCode = 128 + signal;
const done = (force = false) => {
if (force === true || shouldManuallyExit === true) {
process.exit(exitCode); // eslint-disable-line unicorn/no-process-exit
}
};
for (const callback of callbacks) {
callback();
callback(exitCode);
}
if (exit === true) {
process.exit(128 + signal); // eslint-disable-line unicorn/no-process-exit
if (isSynchronous) {
done();
return;
}
const promises = [];
let forceAfter = 0;
for (const [callback, wait] of asyncCallbacks) {
forceAfter = Math.max(forceAfter, wait);
promises.push(Promise.resolve(callback(exitCode)));
}
// Force exit if we exceeded our wait value
const asyncTimer = setTimeout(() => {
done(true);
}, forceAfter);
await Promise.all(promises);
clearTimeout(asyncTimer);
done();
}
module.exports = callback => {
callbacks.add(callback);
function addHook(options) {
const {onExit, wait, isSynchronous} = options;
const asyncCallbackConfig = [onExit, wait];
if (isSynchronous) {
callbacks.add(onExit);
} else {
asyncCallbacks.add(asyncCallbackConfig);
}
if (!isRegistered) {
isRegistered = true;
process.once('exit', exit);
process.once('SIGINT', exit.bind(null, true, 2));
process.once('SIGTERM', exit.bind(null, true, 15));
// Exit cases that support asynchronous handling
process.once('beforeExit', exit.bind(undefined, true, false, -128));
process.once('SIGINT', exit.bind(undefined, true, false, 2));
process.once('SIGTERM', exit.bind(undefined, true, false, 15));
// PM2 Cluster shutdown message. Caught to support async handlers with pm2, needed because
// explicitly calling process.exit() doesn't trigger the beforeExit event, and the exit
// event cannot support async handlers, since the event loop is never called after it.
// Explicit exit events. Calling will force an immediate exit and run all
// synchronous hooks. Explicit exits must not extend the node process
// artificially. Will log errors if asynchronous calls exist.
process.once('exit', exit.bind(undefined, false, true, 0));
// PM2 Cluster shutdown message. Caught to support async handlers with pm2,
// needed because explicitly calling process.exit() doesn't trigger the
// beforeExit event, and the exit event cannot support async handlers,
// since the event loop is never called after it.
process.on('message', message => {
if (message === 'shutdown') {
exit(true, -128);
exit(true, true, -128);
}

@@ -45,4 +96,39 @@ });

return () => {
callbacks.delete(callback);
if (isSynchronous) {
callbacks.delete(onExit);
} else {
asyncCallbacks.delete(asyncCallbackConfig);
}
};
};
}
export default function exitHook(onExit) {
if (typeof onExit !== 'function') {
throw new TypeError('onExit must be a function');
}
return addHook({
onExit,
isSynchronous: true,
});
}
export function asyncExitHook(onExit, options = {}) {
if (typeof onExit !== 'function') {
throw new TypeError('onExit must be a function');
}
if (!(typeof options.wait === 'number' && options.wait > 0)) {
throw new TypeError('wait must be set to a positive numeric value');
}
return addHook({
onExit,
wait: options.wait,
isSynchronous: false,
});
}
export function gracefulExit(signal = 0) {
exit(true, false, -128 + signal);
}

21

package.json
{
"name": "exit-hook",
"version": "2.2.1",
"version": "4.0.0",
"description": "Run some code when the process exits",

@@ -13,4 +13,9 @@ "license": "MIT",

},
"type": "module",
"exports": {
"types": "./index.d.ts",
"default": "./index.js"
},
"engines": {
"node": ">=6"
"node": ">=18"
},

@@ -38,10 +43,12 @@ "scripts": {

"event",
"signal"
"signal",
"async",
"asynchronous"
],
"devDependencies": {
"ava": "^1.4.1",
"execa": "^1.0.0",
"tsd": "^0.7.2",
"xo": "^0.24.0"
"ava": "^5.3.1",
"execa": "^8.0.1",
"tsd": "^0.28.1",
"xo": "^0.56.0"
}
}

@@ -11,5 +11,5 @@ # exit-hook

```sh
npm install exit-hook
```
$ npm install exit-hook
```

@@ -19,6 +19,6 @@ ## Usage

```js
const exitHook = require('exit-hook');
import exitHook from 'exit-hook';
exitHook(() => {
console.log('Exiting');
exitHook(signal => {
console.log(`Exiting with signal: ${signal}`);
});

@@ -40,3 +40,3 @@

```js
const exitHook = require('exit-hook');
import exitHook from 'exit-hook';

@@ -50,22 +50,93 @@ const unsubscribe = exitHook(() => {});

### exitHook(callback)
### exitHook(onExit)
Register a function to run during `process.exit`.
Returns a function that removes the hook when called.
#### callback
#### onExit
Type: `Function`
Type: `(signal: number) => void`
The callback to execute when the process exits.
The callback function to execute when the process exits.
---
### asyncExitHook(onExit, options)
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-exit-hook?utm_source=npm-exit-hook&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>
Register a function to run during `gracefulExit`.
Returns a function that removes the hook when called.
Please see [Async Notes](#asynchronous-exit-notes) for considerations when using the asynchronous API.
#### onExit
Type: `(signal: number) => (void | Promise<void>)`
The callback function to execute when the process exits via `gracefulExit`, and will be wrapped in `Promise.resolve`.
#### options
Type: `object`
##### wait
Type: `number`
The amount of time in milliseconds that the `onExit` function is expected to take. When multiple async handlers are registered, the longest `wait` time will be used.
```js
import {asyncExitHook} from 'exit-hook';
asyncExitHook(async () => {
console.log('Exiting');
}, {
wait: 300
});
throw new Error('🦄');
//=> 'Exiting'
```
Removing an asynchronous exit hook:
```js
import {asyncExitHook} from 'exit-hook';
const unsubscribe = asyncExitHook(async () => {
console.log('Exiting');
}, {
wait: 300
});
unsubscribe();
```
### gracefulExit(signal?: number): void
Exit the process and make a best-effort to complete all asynchronous hooks.
If you are using `asyncExitHook`, consider using `gracefulExit()` instead of `process.exit()` to ensure all asynchronous tasks are given an opportunity to run.
```js
import {gracefulExit} from 'exit-hook';
gracefulExit();
```
#### signal
Type: `number`\
Default: `0`
The exit code to use. Same as the argument to `process.exit()`.
## Asynchronous Exit Notes
**tl;dr** If you have 100% control over how your process terminates, then you can swap `exitHook` and `process.exit` for `asyncExitHook` and `gracefulExit` respectively. Otherwise, keep reading to understand important tradeoffs if you're using `asyncExitHook`.
Node.js does not offer an asynchronous shutdown API by default [#1](https://github.com/nodejs/node/discussions/29480#discussioncomment-99213) [#2](https://github.com/nodejs/node/discussions/29480#discussioncomment-99217), so `asyncExitHook` and `gracefulExit` will make a "best effort" attempt to shut down the process and run your asynchronous tasks.
If you have asynchronous hooks registered and your Node.js process is terminated in a synchronous manner, a `SYNCHRONOUS TERMINATION NOTICE` error will be logged to the console. To avoid this, ensure you're only exiting via `gracefulExit` or that an upstream process manager is sending a `SIGINT` or `SIGTERM` signal to Node.js.
Asynchronous hooks should make a "best effort" to perform their tasks within the `wait` time, but also be written to assume they may not complete their tasks before termination.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc