├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json ├── test └── index.js └── types.d.ts /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2019 Rasmus Porsager 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![version](https://img.shields.io/npm/v/prexit.svg)]() [![license](https://img.shields.io/github/license/porsager/prexit.svg)]() 2 | 3 | # 🚪 Prexit 4 | 5 | A graceful way to shutdown / handle **PR**ocess **EXIT** - way better than other \*rexits 6 | 7 | ```js 8 | import prexit from 'prexit' 9 | 10 | prexit((signal, [err]) => { 11 | // Do cleanup before shutdown 12 | // Return a promise to delay exit 13 | // set prexit.code to exit with non 0 14 | // eg prexit.code = 1 15 | }) 16 | ``` 17 | 18 | Here's a sample for shutting down an http server and database. 19 | First we stop the http server from accepting any new connections. 20 | Then we gracefully close the database connection to allow any pending queries to resolve. 21 | 22 | ```js 23 | prexit(async () => { 24 | await new Promise(r => server.close(r)) 25 | await db.end({ timeout: 5 }) 26 | }) 27 | ``` 28 | 29 | Prexit is a simple function that takes a callback. This will be called with the signal and exit code / error on the following `process` events. 30 | 31 | `exit | beforeExit | uncaughtException | unhandledRejection | SIGTSTP | SIGQUIT | SIGHUP | SIGTERM | SIGINT` 32 | 33 | You can call prexit as many times as you'd like so you can do cleanup in the relevant places in your code. Prexit will await all promises that callbacks returns, and will ensure they are only called once. After all the promises are settled prexit will call `prexit.ondone()` which defaults to calling `process.exit(prexit.code)`. 34 | 35 | If you need to do synchronous cleanup after any async cleanup and right before prexit.ondone is called, you can use `prexit.last(fn)` 36 | 37 | ```js 38 | prexit.last(() => { 39 | // This will run after any async handlers right 40 | // before exit, meaning only sync cleanup 41 | // eg. (kill child processes) 42 | }) 43 | ``` 44 | 45 | You can also supply optional events to listen for as the first argument of prexit. 46 | 47 | ```js 48 | prexit('uncaughtException', (signal, err) => /* uncaughtException */ ) 49 | prexit(['SIGHUP', 'SIGTERM'], (signal, err) => /* SIGHUP | SIGTERM */ ) 50 | ```` 51 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const handlers = {} 2 | , last = [] 3 | 4 | let finished = false 5 | 6 | export default prexit 7 | 8 | function prexit(signals, fn) { 9 | if (typeof signals === 'function') { 10 | fn = signals 11 | signals = prexit.signals 12 | } 13 | 14 | let called = false 15 | ;['prexit'].concat(signals).forEach(signal => handle(signal, function(signal, error) { 16 | if (called) 17 | return 18 | called = true 19 | return fn(signal, error) 20 | })) 21 | } 22 | 23 | prexit.signals = ['exit', 'beforeExit', 'uncaughtException', 'unhandledRejection', 'SIGTSTP', 'SIGQUIT', 'SIGHUP', 'SIGTERM', 'SIGINT'] 24 | prexit.logExceptions = true 25 | prexit.exiting = false 26 | 27 | prexit.last = addLast 28 | prexit.exit = exit 29 | prexit.ondone = ondone 30 | 31 | function addLast(fn) { 32 | last.length || prexit(() => {}) 33 | last.push(fn) 34 | } 35 | 36 | function exit(signal, code, error) { 37 | if (typeof signal === 'number') { 38 | error = code 39 | code = signal 40 | signal = 'prexit' 41 | } 42 | 43 | code && (process.exitCode = code) 44 | Object.keys(handlers).length 45 | ? process.emit('prexit', error) 46 | : process.exit() 47 | } 48 | 49 | function ondone(signal, error) { 50 | process.exit() // eslint-disable-line 51 | } 52 | 53 | function handle(signal, fn) { 54 | const handler = handlers[signal] 55 | if (handler) 56 | return handler.push(fn) 57 | 58 | const fns = handlers[signal] = [fn] 59 | 60 | process.on(signal, async function(error) { 61 | prexit.exiting = true 62 | error === signal && (error = null) 63 | if ((signal === 'uncaughtException' || signal === 'unhandledRejection') && prexit.logExceptions) 64 | console.error(error) // eslint-disable-line 65 | 66 | try { 67 | const xs = fns.map(fn => fn(signal, error)).filter(x => x && typeof x.then === 'function') 68 | xs.length && await Promise.all(xs) 69 | } catch (error) { 70 | process.exitCode || (process.exitCode = 1) 71 | prexit.logExceptions && console.error(error) // eslint-disable-line 72 | } 73 | 74 | done(signal, error) 75 | }) 76 | } 77 | 78 | function done(signal, error) { 79 | if (finished) 80 | return 81 | 82 | finished = true 83 | try { 84 | last.forEach(fn => fn(signal)) 85 | } catch (err) { 86 | error 87 | ? console.error(err) // eslint-disable-line 88 | : error = err 89 | } 90 | 91 | prexit.ondone(signal, error) 92 | } 93 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prexit", 3 | "version": "2.3.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prexit", 3 | "version": "2.3.0", 4 | "description": "A graceful way to shutdown / handle process exit", 5 | "main": "index.js", 6 | "types": "types.d.ts", 7 | "type": "module", 8 | "scripts": { 9 | "test": "node test" 10 | }, 11 | "author": "Rasmus Porsager ", 12 | "license": "WTFPL", 13 | "repository": "porsager/prexit", 14 | "engines": { 15 | "node": ">=12" 16 | }, 17 | "tags": [ 18 | "process", 19 | "exit", 20 | "graceful", 21 | "shutdown", 22 | "SIGTERM", 23 | "SIGHUP", 24 | "SIGINT", 25 | "prexit", 26 | "unhandledException" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import prexit from '../index.js' 2 | 3 | prexit((...args) => { 4 | console.log('Received', args) 5 | return new Promise(resolve => { 6 | setTimeout(() => { 7 | console.log('before resolve') 8 | resolve() 9 | setTimeout(() => console.log('after resolve'), 0) 10 | }, 1000) 11 | }) 12 | }) 13 | 14 | setTimeout(() => {}, 1000000) 15 | 16 | prexit('SIGINT', (...args) => { 17 | console.log('Received2', args) 18 | return new Promise(resolve => { 19 | setTimeout(() => { 20 | console.log('before resolve2') 21 | resolve() 22 | }, 1500) 23 | }) 24 | }) 25 | 26 | setTimeout(() => { 27 | prexit.exit() 28 | }, 1500) 29 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | type PrexitSignal = 2 | | "beforeExit" 3 | | "uncaughtException" 4 | | "SIGTSTP" 5 | | "SIGQUIT" 6 | | "SIGHUP" 7 | | "SIGTERM" 8 | | "SIGINT"; 9 | 10 | declare module "prexit" { 11 | interface Prexit { 12 | code: number; 13 | exiting: boolean; 14 | signals: PrexitSignal[]; 15 | logExceptions: boolean; 16 | 17 | ondone(): void; 18 | last( 19 | callback?: (signal: PrexitSignal, code: number, error?: Error) => void 20 | ): void; 21 | exit(signal?: PrexitSignal, code?: number): void; 22 | exit(code: number): void; 23 | (callback?: (signal: PrexitSignal, code: number, error?: Error) => unknown | Promise): void; 24 | (event: string | string[], callback?: (signal: PrexitSignal, code: number, error?: Error) => unknown | Promise): void; 25 | } 26 | 27 | const prexit: Prexit; 28 | 29 | export default prexit; 30 | } 31 | --------------------------------------------------------------------------------