Socket
Socket
Sign inDemoInstall

exit-hook

Package Overview
Dependencies
0
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.0.0 to 3.1.0

60

index.d.ts

@@ -6,3 +6,3 @@ /**

This package is useful for cleaning up before exiting.
This is useful for cleaning synchronously before exiting.

@@ -37,1 +37,59 @@ @param onExit - The callback function to execute when the process exits.

export default function exitHook(onExit: () => void): () => void;
/**
Run code asynchronously when the process exits.
@see https://github.com/sindresorhus/exit-hook/blob/main/readme.md#asynchronous-exit-notes
@param 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');
}, {
minimumWait: 500
});
throw new Error('🦄');
//=> 'Exiting'
// Removing an exit hook:
const unsubscribe = asyncExitHook(() => {}, {});
unsubscribe();
```
*/
export function asyncExitHook(onExit: () => (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');
}, 500);
// Instead of `process.exit()`
gracefulExit();
```
*/
export function gracefulExit(signal?: number): void;
export interface Options {
/**
The amount of time in milliseconds that the `onExit` function is expected to take.
*/
minimumWait: number;
}
import process from 'node:process';
const asyncCallbacks = new Set();
const callbacks = new Set();
let isCalled = false;
let isRegistered = false;
function exit(shouldManuallyExit, signal) {
async function exit(shouldManuallyExit, isSynchronous, signal) {
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(' '));
}
if (isCalled) {

@@ -14,2 +26,8 @@ return;

const done = (force = false) => {
if (force === true || shouldManuallyExit === true) {
process.exit(128 + signal); // eslint-disable-line unicorn/no-process-exit
}
};
for (const callback of callbacks) {

@@ -19,23 +37,54 @@ callback();

if (shouldManuallyExit === 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()));
}
// Force exit if we exceeded our wait value
const asyncTimer = setTimeout(() => {
done(true);
}, forceAfter);
await Promise.all(promises);
clearTimeout(asyncTimer);
done();
}
export default function exitHook(onExit) {
callbacks.add(onExit);
function addHook(options) {
const {onExit, minimumWait, isSynchronous} = options;
const asyncCallbackConfig = [onExit, minimumWait];
if (isSynchronous) {
callbacks.add(onExit);
} else {
asyncCallbacks.add(asyncCallbackConfig);
}
if (!isRegistered) {
isRegistered = true;
process.once('exit', exit);
process.once('SIGINT', exit.bind(undefined, true, 2));
process.once('SIGTERM', exit.bind(undefined, true, 15));
// Exit cases that support asynchronous handling
process.once('beforeExit', exit.bind(undefined, true, false, 0));
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);
}

@@ -46,4 +95,39 @@ });

return () => {
callbacks.delete(onExit);
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?.minimumWait !== 'number' || options.minimumWait <= 0) {
throw new TypeError('minimumWait must be set to a positive numeric value');
}
return addHook({
onExit,
minimumWait: options.minimumWait,
isSynchronous: false,
});
}
export function gracefulExit(signal = 0) {
exit(true, false, -128 + signal);
}

6

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

@@ -39,3 +39,5 @@ "license": "MIT",

"event",
"signal"
"signal",
"async",
"asynchronous"
],

@@ -42,0 +44,0 @@ "devDependencies": {

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

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

@@ -50,2 +50,4 @@ ## Usage

Register a function to run during `process.exit`.
Returns a function that removes the hook when called.

@@ -55,16 +57,81 @@

Type: `Function`
Type: `function(): void`
The callback function to execute when the process exits.
---
### asyncExitHook(onExit, minimumWait)
<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.
```js
import {asyncExitHook} from 'exit-hook';
asyncExitHook(async () => {
console.log('Exiting');
}, 300);
throw new Error('🦄');
//=> 'Exiting'
```
Removing an asynchronous exit hook:
```js
import {asyncExitHook} from 'exit-hook';
const unsubscribe = asyncExitHook(async () => {
console.log('Exiting');
}, {
minimumWait: 300
});
unsubscribe();
```
#### onExit
Type: `function(): void | Promise<void>`
The callback function to execute when the process exits via `gracefulExit`, and will be wrapped in `Promise.resolve`.
#### options
##### minimumWait
Type: `number`
The amount of time in milliseconds that the `onExit` function is expected to take.
### 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 `minimumWait` 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

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc