├── .nvmrc ├── .node-version ├── tslint.json ├── .npmignore ├── tests ├── unhandled-rejection-disabled.js ├── uncaught-error.js ├── async-exit.js ├── unhandled-rejection.js ├── self-triggered.js ├── sighup-exit.js ├── sigint-exit.js ├── sigterm-exit.js ├── simple-exit.js ├── unhandled-rejection-toggled.js ├── multiple-errors.js ├── uncaught-error-disabled.js ├── forced-exit.js ├── exit-on-double.js ├── clear-listeners.js ├── uncaught-error-toggled.js ├── exit-timeout.js ├── multiple-listeners.js ├── wait-for-promise.js ├── wait-for-multiple-promise.ts ├── TypeScript.ts └── runner.js ├── .github └── workflows │ └── pr.yml ├── tsconfig.json ├── LICENSE ├── index.d.ts ├── package.json ├── .gitignore ├── index.ts ├── index.js └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.* -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tests/ 2 | .gitignore 3 | .npmignore 4 | .travis.yml 5 | LICENSE 6 | tslint.json 7 | package-lock.json 8 | tsconfig.json 9 | -------------------------------------------------------------------------------- /tests/unhandled-rejection-disabled.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | Graceful.on('exit', () => { 6 | process.stdout.write('ok'); 7 | }); 8 | 9 | setTimeout(() => process.exit(0), 100); 10 | 11 | Promise.reject(new Error('DANG!')); 12 | -------------------------------------------------------------------------------- /tests/uncaught-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | Graceful.captureExceptions = true; 5 | 6 | 7 | Graceful.on('exit', () => { 8 | process.stdout.write('ok'); 9 | }); 10 | 11 | setTimeout(() => {}, 99999999); // keep event-loop running endlessly 12 | 13 | throw new Error('DANG!'); 14 | -------------------------------------------------------------------------------- /tests/async-exit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | Graceful.on('exit', done => { 6 | return new Promise((resolve) => { 7 | setTimeout(() => { 8 | process.stdout.write('ok'); 9 | resolve(); 10 | }, 100) 11 | }); 12 | }); 13 | 14 | Graceful.exit(); 15 | -------------------------------------------------------------------------------- /tests/unhandled-rejection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | Graceful.captureRejections = true; 5 | 6 | 7 | Graceful.on('exit', () => { 8 | process.stdout.write('ok'); 9 | }); 10 | 11 | setTimeout(() => {}, 99999999); // keep event-loop running endlessly 12 | 13 | Promise.reject(new Error('DANG!')); 14 | -------------------------------------------------------------------------------- /tests/self-triggered.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | Graceful.on('exit', () => { 6 | return new Promise((resolve) => { 7 | setTimeout(() => { 8 | process.stdout.write('ok'); 9 | resolve(); 10 | }, 100); 11 | }); 12 | }); 13 | 14 | process.emit('SIGTERM'); 15 | -------------------------------------------------------------------------------- /tests/sighup-exit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | Graceful.on('exit', () => { 6 | return new Promise((resolve) => { 7 | setTimeout(() => { 8 | process.stdout.write('ok'); 9 | resolve(); 10 | }, 100); 11 | }); 12 | }); 13 | 14 | setTimeout(() => {}, 99999999); 15 | -------------------------------------------------------------------------------- /tests/sigint-exit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | Graceful.on('exit', () => { 6 | return new Promise((resolve) => { 7 | setTimeout(() => { 8 | process.stdout.write('ok'); 9 | resolve(); 10 | }, 100) 11 | }); 12 | }); 13 | 14 | setTimeout(() => {}, 99999999); 15 | -------------------------------------------------------------------------------- /tests/sigterm-exit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | Graceful.on('exit', () => { 6 | return new Promise((resolve) => { 7 | setTimeout(() => { 8 | process.stdout.write('ok'); 9 | resolve(); 10 | }, 100); 11 | }); 12 | }); 13 | 14 | setTimeout(() => {}, 99999999); 15 | -------------------------------------------------------------------------------- /tests/simple-exit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | Graceful.on('exit', done => { 6 | return new Promise((resolve) => { 7 | setTimeout(() => { 8 | process.stdout.write('ok'); 9 | resolve(); 10 | }, 100) 11 | }); 12 | }); 13 | 14 | setTimeout(() => null,99999999); 15 | -------------------------------------------------------------------------------- /tests/unhandled-rejection-toggled.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | Graceful.captureRejections = true; 5 | Graceful.captureRejections = false; 6 | 7 | Graceful.on('exit', () => { 8 | process.stdout.write('ok'); 9 | }); 10 | 11 | setTimeout(() => process.exit(0), 100); 12 | 13 | Promise.reject(new Error('DANG!')); 14 | -------------------------------------------------------------------------------- /tests/multiple-errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | Graceful.captureRejections = Graceful.captureExceptions = true; 5 | 6 | Graceful.on('exit', () => { 7 | process.stdout.write('error'); 8 | }); 9 | 10 | setTimeout(() => {}, 99999999); // keep event-loop running endlessly 11 | 12 | Promise.reject(new Error('DANG!')); 13 | -------------------------------------------------------------------------------- /tests/uncaught-error-disabled.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | Graceful.on('exit', () => { 6 | process.stdout.write('ok'); 7 | }); 8 | 9 | process.on('uncaughtException', () => {/* ignore */}); // prevent auto kill behavior on newer node versions 10 | 11 | setTimeout(() => process.exit(0), 100); 12 | 13 | throw new Error('DANG!'); 14 | -------------------------------------------------------------------------------- /tests/forced-exit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | Graceful.timeout = 1000; 6 | 7 | Graceful.on('exit', () => { 8 | return new Promise((resolve) => { 9 | setTimeout(() => { 10 | process.stdout.write('not-supposed-to-run'); 11 | resolve(); 12 | }, 2000); 13 | }); 14 | }); 15 | 16 | Graceful.exit(); 17 | -------------------------------------------------------------------------------- /tests/exit-on-double.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | Graceful.on('exit', () => { 6 | return new Promise((resolve) => { 7 | setTimeout(() => { 8 | process.stdout.write('should-not-run'); 9 | resolve(); 10 | }, 1000) 11 | }); 12 | }); 13 | 14 | 15 | Graceful.exit('SIGBREAK'); 16 | 17 | setTimeout(() => Graceful.exit('SIGBREAK'), 500); 18 | -------------------------------------------------------------------------------- /tests/clear-listeners.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | let handler = () => { 6 | return new Promise((resolve) => { 7 | process.stdout.write('should-not-run'); 8 | resolve(); 9 | }); 10 | }; 11 | 12 | Graceful.on('exit', handler); 13 | Graceful.off('exit', handler); 14 | 15 | let removeListener = Graceful.on('exit', handler.bind(null)); 16 | removeListener(); 17 | 18 | Graceful.exit(); 19 | -------------------------------------------------------------------------------- /tests/uncaught-error-toggled.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | Graceful.captureExceptions = true; 5 | Graceful.captureExceptions = false; 6 | 7 | Graceful.on('exit', () => { 8 | process.stdout.write('ok'); 9 | }); 10 | 11 | process.on('uncaughtException', () => {/* ignore */}); // prevent auto kill behavior on newer node versions 12 | 13 | setTimeout(() => process.exit(0), 100); 14 | 15 | throw new Error('DANG!'); 16 | -------------------------------------------------------------------------------- /tests/exit-timeout.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | Graceful.timeout = 50; 5 | 6 | Graceful.on('exit', done => { 7 | return new Promise((resolve) => { 8 | setTimeout(() => { 9 | process.stdout.write('ok1'); 10 | resolve(); 11 | }, 10) 12 | }); 13 | }); 14 | 15 | Graceful.on('exit', done => { 16 | return new Promise((resolve) => { 17 | setTimeout(() => { 18 | process.stdout.write('ok2'); 19 | resolve(); 20 | }, 100) 21 | }); 22 | }); 23 | 24 | setTimeout(() => {}, 99999999); 25 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node: [ '8', '10', '12', '14', '16' ] 17 | name: Node ${{ matrix.node }} Tests 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Setup node 21 | uses: actions/setup-node@v2 22 | with: 23 | node-version: ${{ matrix.node }} 24 | cache: 'npm' 25 | - run: npm ci 26 | - run: npm run test -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "es5", 5 | "strict": true, 6 | "alwaysStrict": true, 7 | "preserveConstEnums": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noUncheckedIndexedAccess": true, 10 | "sourceMap": false, 11 | "moduleResolution": "Node", 12 | "esModuleInterop": true, 13 | "declaration": true, 14 | "pretty": true, 15 | "lib": [ 16 | "es5" 17 | ] 18 | }, 19 | "include": [ 20 | "./**/*.ts" 21 | ], 22 | "exclude": [ 23 | "node_modules", 24 | "tests/wait-for-multiple-promise.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /tests/multiple-listeners.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | let count = 0; 6 | Graceful.on('exit', () => { 7 | return new Promise((resolve) => { 8 | setTimeout(() => { 9 | count++; 10 | resolve(); 11 | }, 100); 12 | }); 13 | }); 14 | 15 | Graceful.on('exit', () => { 16 | return new Promise((resolve) => { 17 | setTimeout(() => { 18 | count++; 19 | if (count === 2) { 20 | process.stdout.write('ok'); 21 | } 22 | resolve(); 23 | }, 200); 24 | }); 25 | }); 26 | 27 | Graceful.exit('SIGINT'); 28 | -------------------------------------------------------------------------------- /tests/wait-for-promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Graceful = require('../'); 4 | 5 | let handlerSuccess = () => { 6 | return new Promise(function (resolve) { 7 | setTimeout(() => { 8 | process.stdout.write('ok'); 9 | resolve(); 10 | }, 100) 11 | }) 12 | }; 13 | 14 | let handlerReject = () => { 15 | return new Promise(function (resolve, reject) { 16 | setTimeout(() => { 17 | process.stdout.write('ok'); 18 | reject(); 19 | }, 200) 20 | }) 21 | }; 22 | 23 | Graceful.on('exit', handlerSuccess); 24 | Graceful.on('exit', handlerReject); 25 | 26 | Graceful.exit(); 27 | -------------------------------------------------------------------------------- /tests/wait-for-multiple-promise.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Graceful from "../"; 4 | 5 | const handlerSuccess = (): Promise => { 6 | return new Promise((resolve) => { 7 | setTimeout(() => { 8 | process.stdout.write("ok"); 9 | resolve(); 10 | }, 100); 11 | }); 12 | }; 13 | 14 | const handlerReject = (): Promise => { 15 | return new Promise((_resolve, reject) => { 16 | setTimeout(() => { 17 | process.stdout.write("ok"); 18 | reject(); 19 | }, 200); 20 | }); 21 | }; 22 | 23 | Graceful.on("exit", handlerSuccess); 24 | Graceful.on("exit", handlerReject); 25 | 26 | Graceful.exit(); 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 MrBar42 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/TypeScript.ts: -------------------------------------------------------------------------------- 1 | import Graceful from "../"; 2 | 3 | const listener = (signal: string, event?: object) => void 0; 4 | const listenerPromise = (signal: string, event?: object) => Promise.resolve("abc"); 5 | const listenerPromise1 = (signal: string, event?: object) => Promise.resolve({}); 6 | const listenerPromise2 = (signal: string, event?: object) => new Promise((resolve) => resolve()); 7 | const listenerPromise3 = (signal: string, event?: object) => new Promise((resolve) => resolve()); 8 | const listenerPromise4 = () => null; 9 | 10 | Graceful.on("exit", listener); 11 | Graceful.on("exit", listenerPromise); 12 | Graceful.on("exit", listenerPromise1); 13 | Graceful.on("exit", listenerPromise2); 14 | Graceful.on("exit", listenerPromise3); 15 | Graceful.on("exit", listenerPromise4); 16 | 17 | Graceful.off("exit", listener); 18 | 19 | Graceful.clear(); 20 | 21 | Graceful.exit(); 22 | Graceful.exit(1); 23 | Graceful.exit(0, "SIGTERM"); 24 | Graceful.exit("SIGTERM"); 25 | 26 | Graceful.exitOnDouble = true; 27 | Graceful.timeout = 30000; 28 | Graceful.captureExceptions = true; 29 | Graceful.captureRejections = true; 30 | 31 | const indirectRef: typeof Graceful = Graceful; 32 | indirectRef.on("exit", listener); 33 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export default class Graceful { 2 | private static DEADLY_SIGNALS; 3 | static exitOnDouble: boolean; 4 | static timeout: number; 5 | private static _captureExceptions; 6 | private static _captureRejections; 7 | private static listeners; 8 | private static isRegistered; 9 | private static isExiting; 10 | private static exceptionListener; 11 | private static rejectionListener; 12 | private static signalsListeners; 13 | static get captureExceptions(): boolean; 14 | static set captureExceptions(newValue: boolean); 15 | static get captureRejections(): boolean; 16 | static set captureRejections(newValue: boolean); 17 | static on(signal: 'exit', listener: GracefulListener): GracefulSubscription; 18 | static off(signal: 'exit', listener: GracefulListener): void; 19 | static clear(): void; 20 | static exit(code?: number | string, signal?: string): void; 21 | private static onDeadlyEvent; 22 | private static invokeListener; 23 | private static updateRegistration; 24 | private static killProcess; 25 | } 26 | export declare type GracefulListener = (signal: string, details?: object) => (void | any | Promise | Promise | Promise); 27 | export declare type GracefulSubscription = () => void; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-graceful", 3 | "version": "3.1.0", 4 | "description": "Graceful process exit manager. allows waiting on multiple async services.", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/mrbar42/node-graceful.git" 9 | }, 10 | "keywords": [ 11 | "graceful", 12 | "exit", 13 | "signal", 14 | "sigterm", 15 | "SIGINT", 16 | "SIGHUP", 17 | "shutdown", 18 | "graceful exit", 19 | "uncaught", 20 | "uncaughtException", 21 | "unhandledRejection", 22 | "terminate", 23 | "stop", 24 | "process" 25 | ], 26 | "scripts": { 27 | "test:unit": "node tests/runner", 28 | "test:unit-ts": "ts-node tests/wait-for-multiple-promise.ts", 29 | "test:lint": "tsc --noEmit && tslint -c tslint.json --fix tests/*.ts", 30 | "test": "npm run test:lint && npm run test:unit && npm run test:unit-ts", 31 | "build": "tsc --build tsconfig.json", 32 | "prepublishOnly": "npm run build && npm run test" 33 | }, 34 | "files": [ 35 | "index.d.ts", 36 | "index.js", 37 | "README.md" 38 | ], 39 | "author": "mrbar42", 40 | "license": "MIT", 41 | "devDependencies": { 42 | "@types/node": "^16.6.1", 43 | "ts-node": "^10.2.0", 44 | "tslint": "^6.1.3", 45 | "typescript": "^4.3.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 30 | node_modules 31 | 32 | ## Directory-based project format: 33 | .idea/ 34 | # if you remove the above rule, at least ignore the following: 35 | 36 | # User-specific stuff: 37 | # .idea/workspace.xml 38 | # .idea/tasks.xml 39 | # .idea/dictionaries 40 | 41 | # Sensitive or high-churn files: 42 | # .idea/dataSources.ids 43 | # .idea/dataSources.xml 44 | # .idea/sqlDataSources.xml 45 | # .idea/dynamic.xml 46 | # .idea/uiDesigner.xml 47 | 48 | # Gradle: 49 | # .idea/gradle.xml 50 | # .idea/libraries 51 | 52 | # Mongo Explorer plugin: 53 | # .idea/mongoSettings.xml 54 | 55 | ## File-based project format: 56 | *.ipr 57 | *.iws 58 | 59 | ## Plugin-specific files: 60 | 61 | # IntelliJ 62 | /out/ 63 | 64 | # mpeltonen/sbt-idea plugin 65 | .idea_modules/ 66 | 67 | # JIRA plugin 68 | atlassian-ide-plugin.xml 69 | 70 | # Crashlytics plugin (for Android Studio and IntelliJ) 71 | com_crashlytics_export_strings.xml 72 | crashlytics.properties 73 | crashlytics-build.properties 74 | .DS_Store 75 | -------------------------------------------------------------------------------- /tests/runner.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // this is a small testing script to avoid heavy external testing dependencies 4 | 5 | const execFile = require('child_process').execFile; 6 | 7 | let tests = [ 8 | { 9 | name: 'Async wait', 10 | child: './async-exit', 11 | expectedOutput: 'ok', 12 | expectedExitCode: 0 13 | }, 14 | { 15 | name: 'SIGTERM exit', 16 | child: './sigterm-exit', 17 | signal: 'SIGTERM', 18 | expectedOutput: 'ok', 19 | expectedExitCode: 0 20 | }, 21 | { 22 | name: 'SIGINT exit', 23 | child: './sigint-exit', 24 | signal: 'SIGINT', 25 | expectedOutput: 'ok', 26 | expectedExitCode: 0 27 | }, 28 | { 29 | name: 'SIGHUP exit', 30 | child: './sighup-exit', 31 | signal: 'SIGHUP', 32 | expectedOutput: 'ok', 33 | expectedExitCode: 0 34 | }, 35 | { 36 | name: 'Timeout exit', 37 | child: './exit-timeout', 38 | signal: 'SIGTERM', 39 | expectedOutput: 'ok1', 40 | expectedExitCode: 1 41 | }, 42 | { 43 | name: 'Multiple listeners', 44 | child: './multiple-listeners', 45 | expectedOutput: 'ok', 46 | expectedExitCode: 0 47 | }, 48 | { 49 | name: 'Forced exit', 50 | child: './forced-exit', 51 | expectedOutput: '', 52 | expectedExitCode: 1 53 | }, 54 | { 55 | name: 'Self triggered', 56 | child: './self-triggered', 57 | expectedOutput: 'ok', 58 | expectedExitCode: 0 59 | }, 60 | { 61 | name: 'Clear listeners', 62 | child: './clear-listeners', 63 | expectedOutput: '', 64 | expectedExitCode: 0 65 | }, 66 | { 67 | name: 'Exit on double', 68 | child: './exit-on-double', 69 | expectedOutput: '', 70 | expectedExitCode: 1 71 | }, 72 | { 73 | name: 'Wait for promise', 74 | child: './wait-for-promise', 75 | expectedOutput: 'okok', 76 | expectedExitCode: 0 77 | }, 78 | { 79 | name: 'Uncaught Error', 80 | child: './uncaught-error', 81 | expectedOutput: 'ok', 82 | expectedExitCode: 1 83 | }, 84 | { 85 | name: 'Uncaught Error - Disabled', 86 | child: './uncaught-error-disabled', 87 | expectedOutput: '', 88 | expectedExitCode: 0 89 | }, 90 | { 91 | name: 'Uncaught Error - Toggled', 92 | child: './uncaught-error-toggled', 93 | expectedOutput: '', 94 | expectedExitCode: 0 95 | }, 96 | { 97 | name: 'Unhandled Rejection', 98 | child: './unhandled-rejection', 99 | expectedOutput: 'ok', 100 | expectedExitCode: 1 101 | }, 102 | { 103 | name: 'Unhandled Rejection - Disabled', 104 | child: './unhandled-rejection-disabled', 105 | expectedOutput: '', 106 | expectedExitCode: 0 107 | }, 108 | { 109 | name: 'Unhandled Rejection - Toggled', 110 | child: './unhandled-rejection-toggled', 111 | expectedOutput: '', 112 | expectedExitCode: 0 113 | }, 114 | { 115 | name: 'Multiple Errors', 116 | child: './multiple-errors', 117 | expectedOutput: 'error', 118 | expectedExitCode: 1 119 | } 120 | ]; 121 | 122 | let total = tests.length; 123 | let count = 0; 124 | let success = 0; 125 | 126 | function asyncRunner() { 127 | let test = tests.shift(); 128 | if (!test) { 129 | if (success < total) { 130 | console.log(`Has ${total - success} errors!`); 131 | process.exit(1); 132 | } else { 133 | console.log('Success!'); 134 | process.exit(0); 135 | } 136 | } 137 | 138 | count++; 139 | 140 | let path = require.resolve(__dirname + '/' + test.child); 141 | let timer; 142 | let ended = false; 143 | const args = [path]; 144 | // unhandled rejections default to throw since v15, warn since v10 145 | // we make it the same for all versions so the result will be consistent. 146 | if (Number(process.version.substring(1).split('.')[0]) >= 10) { 147 | args.unshift("--unhandled-rejections=none"); 148 | } 149 | 150 | let child = execFile('node', args, (err, stdout) => { 151 | clearTimeout(timer); 152 | ended = true; 153 | if (err && /Error/.test(err.message)) { 154 | console.error(`[${count}/${total}] [ERROR] ${test.name}: Failed with error\n`, 155 | err.message, 156 | '\n---------------------------------------------------' 157 | ); 158 | } else if (stdout !== test.expectedOutput) { 159 | console.error(`[${count}/${total}] [ERROR] ${test.name}: Wrong output. expected '${test.expectedOutput}' got '${stdout 160 | || ''}'`); 161 | } else if (child.exitCode !== test.expectedExitCode) { 162 | console.error(`[${count}/${total}] [ERROR] ${test.name}: Wrong exit code. expected ${test.expectedExitCode} got ${child.exitCode}`); 163 | } else { 164 | success++; 165 | console.log(`[${count}/${total}] [OK] ${test.name}`); 166 | } 167 | 168 | asyncRunner(); 169 | }); 170 | 171 | if (test.signal) { 172 | setTimeout(() => { 173 | if (ended) return; 174 | process.kill(child.pid, test.signal); 175 | }, 500); 176 | } 177 | 178 | timer = setTimeout(() => { 179 | throw new Error('Test took too long'); 180 | }, 5000); 181 | } 182 | 183 | asyncRunner(); 184 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | // +----------------------------------------------------------------------+ 2 | // | node-graceful v3 (https://github.com/mrbar42/node-graceful) | 3 | // | Graceful process exit manager. | 4 | // |----------------------------------------------------------------------| 5 | 'use strict'; 6 | 7 | export default class Graceful { 8 | private static DEADLY_SIGNALS = ['SIGTERM', 'SIGINT', 'SIGBREAK', 'SIGHUP']; 9 | 10 | public static exitOnDouble = true; 11 | public static timeout = 30000; 12 | 13 | private static _captureExceptions = false; 14 | private static _captureRejections = false; 15 | private static listeners: GracefulListener[] = []; 16 | private static isRegistered = false; 17 | private static isExiting = false; 18 | private static exceptionListener = (event: any) => { 19 | process.exitCode = 1; 20 | Graceful.onDeadlyEvent('uncaughtException', event); 21 | }; 22 | private static rejectionListener = (event: any) => { 23 | process.exitCode = 1; 24 | Graceful.onDeadlyEvent('unhandledRejection', event) 25 | }; 26 | private static signalsListeners: { [signal: string]: (event: any) => void } = {}; 27 | 28 | public static get captureExceptions() { 29 | return Graceful._captureExceptions; 30 | } 31 | 32 | public static set captureExceptions(newValue: boolean) { 33 | if (Graceful._captureExceptions === newValue) return; 34 | Graceful._captureExceptions = newValue; 35 | 36 | if (Graceful._captureExceptions) { 37 | process.on('uncaughtException' as any, Graceful.exceptionListener); 38 | } else { 39 | process.removeListener('uncaughtException', Graceful.exceptionListener); 40 | } 41 | } 42 | 43 | public static get captureRejections() { 44 | return Graceful._captureRejections; 45 | } 46 | 47 | public static set captureRejections(newValue: boolean) { 48 | if (Graceful._captureRejections === newValue) return; 49 | Graceful._captureRejections = newValue; 50 | 51 | if (Graceful._captureRejections) { 52 | process.on('unhandledRejection' as any, Graceful.rejectionListener); 53 | } else { 54 | process.removeListener('unhandledRejection', Graceful.rejectionListener); 55 | } 56 | } 57 | 58 | public static on(signal: 'exit', listener: GracefulListener): GracefulSubscription { 59 | if (signal !== 'exit') throw new Error('Only supports \'exit\' signal'); 60 | 61 | Graceful.listeners.push(listener); 62 | 63 | Graceful.updateRegistration(); 64 | return () => Graceful.off('exit', listener); 65 | } 66 | 67 | public static off(signal: 'exit', listener: GracefulListener) { 68 | if (signal !== 'exit') throw new Error('Only supports \'exit\' signal'); 69 | 70 | const index = Graceful.listeners.indexOf(listener); 71 | if (index !== -1) Graceful.listeners.splice(index, 1); 72 | 73 | Graceful.updateRegistration(); 74 | } 75 | 76 | public static clear() { 77 | Graceful.listeners.splice(0, Infinity); 78 | Graceful.updateRegistration(); 79 | } 80 | 81 | public static exit(code?: number | string, signal = 'SIGTERM') { 82 | let exitSignal = typeof code === 'string' ? code : signal; 83 | 84 | if (typeof code === 'number') { 85 | process.exitCode = code; 86 | } 87 | 88 | Graceful.onDeadlyEvent(exitSignal, {reason: 'Manual call to Graceful.exit()'}); 89 | } 90 | 91 | private static onDeadlyEvent(signal: string, details?: object) { 92 | // console.log(signal, details); 93 | if (Graceful.isExiting) { 94 | if (Graceful.exitOnDouble) Graceful.killProcess(true); 95 | return 96 | } 97 | 98 | const listeners = Graceful.listeners.slice(0); 99 | 100 | Graceful.isExiting = true; 101 | 102 | let completedListeners = 0; 103 | const done = () => { 104 | completedListeners++; 105 | if (completedListeners === listeners.length) { 106 | Graceful.killProcess(false); 107 | } 108 | }; 109 | 110 | if (Number(Graceful.timeout)) { 111 | const timeoutRef = setTimeout(() => Graceful.killProcess(true), Graceful.timeout); 112 | if (timeoutRef && timeoutRef.unref) timeoutRef.unref(); 113 | } 114 | 115 | for (const listener of listeners) { 116 | Graceful.invokeListener(listener, done, signal, details) 117 | } 118 | } 119 | 120 | private static invokeListener(listener: GracefulListener, done: () => void, signal: string, details?: object) { 121 | let invoked = false; 122 | let listenerDone = () => { 123 | if (!invoked) { 124 | invoked = true; 125 | done(); 126 | } 127 | }; 128 | 129 | const retVal: any = listener(signal, details); 130 | // allow returning a promise 131 | if (retVal && typeof retVal.then === 'function') { 132 | retVal.then(listenerDone, listenerDone); 133 | } else { 134 | listenerDone(); 135 | } 136 | } 137 | 138 | private static updateRegistration() { 139 | if (Graceful.listeners.length && !Graceful.isRegistered) { 140 | for (const deadlySignal of Graceful.DEADLY_SIGNALS) { 141 | const listener = () => Graceful.onDeadlyEvent(deadlySignal); 142 | Graceful.signalsListeners[deadlySignal] = listener 143 | process.on(deadlySignal as any, listener); 144 | } 145 | Graceful.isRegistered = true 146 | } else if (!Graceful.listeners.length && Graceful.isRegistered) { 147 | for (const deadlySignal of Graceful.DEADLY_SIGNALS) { 148 | const listener = Graceful.signalsListeners[deadlySignal] 149 | if (listener) { 150 | process.removeListener(deadlySignal, listener); 151 | delete Graceful.signalsListeners[deadlySignal]; 152 | } 153 | } 154 | Graceful.isRegistered = false 155 | } 156 | } 157 | 158 | private static killProcess(force: boolean) { 159 | process.exit(process.exitCode || (force ? 1 : 0)); 160 | } 161 | } 162 | 163 | // @ts-ignore - simulate default export in way Typescript can't understand 164 | Graceful.default = exports.default; 165 | 166 | // Support all the possible commonjs variations including Typescript 167 | module.exports = Graceful; 168 | Object.defineProperty(Graceful, "__esModule", {value: true}); 169 | 170 | export type GracefulListener = (signal: string, details?: object) 171 | => (void | any | Promise | Promise | Promise) 172 | 173 | export type GracefulSubscription = () => void 174 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // +----------------------------------------------------------------------+ 2 | // | node-graceful v3 (https://github.com/mrbar42/node-graceful) | 3 | // | Graceful process exit manager. | 4 | // |----------------------------------------------------------------------| 5 | 'use strict'; 6 | Object.defineProperty(exports, "__esModule", { value: true }); 7 | var Graceful = /** @class */ (function () { 8 | function Graceful() { 9 | } 10 | Object.defineProperty(Graceful, "captureExceptions", { 11 | get: function () { 12 | return Graceful._captureExceptions; 13 | }, 14 | set: function (newValue) { 15 | if (Graceful._captureExceptions === newValue) 16 | return; 17 | Graceful._captureExceptions = newValue; 18 | if (Graceful._captureExceptions) { 19 | process.on('uncaughtException', Graceful.exceptionListener); 20 | } 21 | else { 22 | process.removeListener('uncaughtException', Graceful.exceptionListener); 23 | } 24 | }, 25 | enumerable: false, 26 | configurable: true 27 | }); 28 | Object.defineProperty(Graceful, "captureRejections", { 29 | get: function () { 30 | return Graceful._captureRejections; 31 | }, 32 | set: function (newValue) { 33 | if (Graceful._captureRejections === newValue) 34 | return; 35 | Graceful._captureRejections = newValue; 36 | if (Graceful._captureRejections) { 37 | process.on('unhandledRejection', Graceful.rejectionListener); 38 | } 39 | else { 40 | process.removeListener('unhandledRejection', Graceful.rejectionListener); 41 | } 42 | }, 43 | enumerable: false, 44 | configurable: true 45 | }); 46 | Graceful.on = function (signal, listener) { 47 | if (signal !== 'exit') 48 | throw new Error('Only supports \'exit\' signal'); 49 | Graceful.listeners.push(listener); 50 | Graceful.updateRegistration(); 51 | return function () { return Graceful.off('exit', listener); }; 52 | }; 53 | Graceful.off = function (signal, listener) { 54 | if (signal !== 'exit') 55 | throw new Error('Only supports \'exit\' signal'); 56 | var index = Graceful.listeners.indexOf(listener); 57 | if (index !== -1) 58 | Graceful.listeners.splice(index, 1); 59 | Graceful.updateRegistration(); 60 | }; 61 | Graceful.clear = function () { 62 | Graceful.listeners.splice(0, Infinity); 63 | Graceful.updateRegistration(); 64 | }; 65 | Graceful.exit = function (code, signal) { 66 | if (signal === void 0) { signal = 'SIGTERM'; } 67 | var exitSignal = typeof code === 'string' ? code : signal; 68 | if (typeof code === 'number') { 69 | process.exitCode = code; 70 | } 71 | Graceful.onDeadlyEvent(exitSignal, { reason: 'Manual call to Graceful.exit()' }); 72 | }; 73 | Graceful.onDeadlyEvent = function (signal, details) { 74 | // console.log(signal, details); 75 | if (Graceful.isExiting) { 76 | if (Graceful.exitOnDouble) 77 | Graceful.killProcess(true); 78 | return; 79 | } 80 | var listeners = Graceful.listeners.slice(0); 81 | Graceful.isExiting = true; 82 | var completedListeners = 0; 83 | var done = function () { 84 | completedListeners++; 85 | if (completedListeners === listeners.length) { 86 | Graceful.killProcess(false); 87 | } 88 | }; 89 | if (Number(Graceful.timeout)) { 90 | var timeoutRef = setTimeout(function () { return Graceful.killProcess(true); }, Graceful.timeout); 91 | if (timeoutRef && timeoutRef.unref) 92 | timeoutRef.unref(); 93 | } 94 | for (var _i = 0, listeners_1 = listeners; _i < listeners_1.length; _i++) { 95 | var listener = listeners_1[_i]; 96 | Graceful.invokeListener(listener, done, signal, details); 97 | } 98 | }; 99 | Graceful.invokeListener = function (listener, done, signal, details) { 100 | var invoked = false; 101 | var listenerDone = function () { 102 | if (!invoked) { 103 | invoked = true; 104 | done(); 105 | } 106 | }; 107 | var retVal = listener(signal, details); 108 | // allow returning a promise 109 | if (retVal && typeof retVal.then === 'function') { 110 | retVal.then(listenerDone, listenerDone); 111 | } 112 | else { 113 | listenerDone(); 114 | } 115 | }; 116 | Graceful.updateRegistration = function () { 117 | if (Graceful.listeners.length && !Graceful.isRegistered) { 118 | var _loop_1 = function (deadlySignal) { 119 | var listener = function () { return Graceful.onDeadlyEvent(deadlySignal); }; 120 | Graceful.signalsListeners[deadlySignal] = listener; 121 | process.on(deadlySignal, listener); 122 | }; 123 | for (var _i = 0, _a = Graceful.DEADLY_SIGNALS; _i < _a.length; _i++) { 124 | var deadlySignal = _a[_i]; 125 | _loop_1(deadlySignal); 126 | } 127 | Graceful.isRegistered = true; 128 | } 129 | else if (!Graceful.listeners.length && Graceful.isRegistered) { 130 | for (var _b = 0, _c = Graceful.DEADLY_SIGNALS; _b < _c.length; _b++) { 131 | var deadlySignal = _c[_b]; 132 | var listener = Graceful.signalsListeners[deadlySignal]; 133 | if (listener) { 134 | process.removeListener(deadlySignal, listener); 135 | delete Graceful.signalsListeners[deadlySignal]; 136 | } 137 | } 138 | Graceful.isRegistered = false; 139 | } 140 | }; 141 | Graceful.killProcess = function (force) { 142 | process.exit(process.exitCode || (force ? 1 : 0)); 143 | }; 144 | Graceful.DEADLY_SIGNALS = ['SIGTERM', 'SIGINT', 'SIGBREAK', 'SIGHUP']; 145 | Graceful.exitOnDouble = true; 146 | Graceful.timeout = 30000; 147 | Graceful._captureExceptions = false; 148 | Graceful._captureRejections = false; 149 | Graceful.listeners = []; 150 | Graceful.isRegistered = false; 151 | Graceful.isExiting = false; 152 | Graceful.exceptionListener = function (event) { 153 | process.exitCode = 1; 154 | Graceful.onDeadlyEvent('uncaughtException', event); 155 | }; 156 | Graceful.rejectionListener = function (event) { 157 | process.exitCode = 1; 158 | Graceful.onDeadlyEvent('unhandledRejection', event); 159 | }; 160 | Graceful.signalsListeners = {}; 161 | return Graceful; 162 | }()); 163 | exports.default = Graceful; 164 | // @ts-ignore - simulate default export in way Typescript can't understand 165 | Graceful.default = exports.default; 166 | // Support all the possible commonjs variations including Typescript 167 | module.exports = Graceful; 168 | Object.defineProperty(Graceful, "__esModule", { value: true }); 169 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-graceful 2 | 3 | [![Build Status](https://travis-ci.org/mrbar42/node-graceful.svg?branch=master)](https://travis-ci.org/mrbar42/node-graceful) [![npm](https://img.shields.io/npm/v/node-graceful.svg)](https://www.npmjs.com/package/node-graceful) ![npm bundle size (version)](https://img.shields.io/bundlephobia/minzip/node-graceful?label=Install%20size) 4 | 5 | node-graceful is a small helper module without dependencies that aims to ease graceful exit 6 | of complex node programs including async waiting on multiple independent modules. 7 | 8 | Installation: 9 | ```sh 10 | npm i -S node-graceful 11 | 12 | yarn add node-graceful 13 | ``` 14 | 15 | Had any problem? open an [issue](https://github.com/mrbar42/node-graceful/issues/new) 16 | 17 | ## Quick example 18 | 19 | Typescript 20 | ```typescript 21 | import Graceful from 'node-graceful'; 22 | Graceful.captureExceptions = true; 23 | 24 | Graceful.on('exit', async () => { 25 | await server.close(); 26 | }); 27 | ``` 28 | 29 | Plain JS 30 | ```javascript 31 | const Graceful = require('node-graceful'); 32 | Graceful.captureExceptions = true; 33 | 34 | Graceful.on('exit', async () => { 35 | console.log(`Received ${signal} - Exiting gracefully`); 36 | await webServer.close(); 37 | }); 38 | 39 | // Graceful will wait until all listeners had finished 40 | Graceful.on('exit', (signal) => { 41 | return new Promise((resolve) => { 42 | console.log("Another independent listener!"); 43 | setTimeout(() => resolve(), 1000); 44 | }); 45 | }); 46 | ``` 47 | 48 | ## Quick Docs 49 | 50 | ```typescript 51 | interface Graceful { 52 | // add exit listener 53 | on(signal: 'exit', listener: GracefulListener): GracefulSubscription; 54 | // remove exit listener 55 | off(signal: 'exit', listener: GracefulListener): void; 56 | // remove all exit listeners 57 | clear(): void; 58 | // trigger graceful process exit with or without exit code and signal 59 | exit(): void; 60 | exit(exitCode: number): void; 61 | exit(exitSignal: string): void; 62 | exit(exitCode: number, exitSignal: string): void; 63 | 64 | // whether to exit immediately when a second kill signal is received 65 | exitOnDouble: boolean; // default: true 66 | // maximum time to wait before hard-killing the process 67 | timeout: number; // default: 30000 68 | // whether to treat uncaught exceptions as process terminating events 69 | captureExceptions: boolean; // default: false 70 | // whether to treat unhandled promise rejections as process terminating events 71 | captureRejections: boolean; // default: false 72 | } 73 | 74 | type GracefulListener = (signal: string, details?: object) => (void | any | Promise | Promise); 75 | type GracefulSubscription = () => void; 76 | ``` 77 | Read bellow for full API reference. 78 | 79 | ## API Reference 80 | 81 | ### Graceful.on('exit', {Function} listener) 82 | 83 | Add exit listener to be called when process exit is triggered. 84 | `Graceful` listens on all terminating signals and triggers `exit` accordingly. 85 | 86 | Terminating events: `SIGTERM` `SIGINT` `SIGBREAK` `SIGHUP` 87 | 88 | #### Options 89 | - `listener(signal, details?)` - listener function 90 | - `signal` - the signal that triggered the exit. example: 'SIGTERM' 91 | - `details` - optional details provided by the trigger. for example in case of `unhandledException` this will be an error object. on external signal it will be undefined. 92 | 93 | #### Examples 94 | The listener function can return a promise that will delay the process exit until it's fulfilment. 95 | ```typescript 96 | Graceful.on('exit', () => Promise.resolve('I Am A Promise!')); 97 | Graceful.on('exit', async () => { 98 | // async function always returns promise so shutdown will be delayed until this functions ends 99 | await webServer.close(); 100 | 101 | return Promise.all([ 102 | controller.close(), 103 | dbClient.close() 104 | ]); 105 | }); 106 | ``` 107 | 108 | if old style callback is needed, wrap the logic with a promise 109 | ```javascript 110 | const server = require('http').createServer(function (req, res) { 111 | res.write('ok'); 112 | res.end() 113 | }) 114 | Graceful.on('exit', () => { 115 | return new Promise((resolve, reject) => { 116 | server.close((err) => { 117 | if (err) return reject(err); 118 | resolve(); 119 | }); 120 | }); 121 | }); 122 | ``` 123 | 124 | #### Return value 125 | the method returns a function that when invoked, removes the listener subscription. 126 | the function is a shorthand for `.off` method 127 | 128 | ##### example 129 | ```typescript 130 | // use the return value to remove listener 131 | const removeListener = Graceful.on('exit', () => {}); 132 | removeListener(); // listener was removed and will not be triggered 133 | ``` 134 | 135 | ### Graceful.off('exit', {Function} listener) 136 | 137 | Remove a previously subscribed listener. 138 | 139 | ##### example 140 | ```typescript 141 | const gracefulExit = () => { 142 | console.log("exiting!"); 143 | }; 144 | 145 | // add listener 146 | let removeListener = Graceful.on('SIGTERM', gracefulExit); 147 | 148 | // remove listener 149 | Graceful.off('SIGTERM', gracefulExit); 150 | // same as invoking the return value 151 | // removeListener(); 152 | ``` 153 | 154 | 155 | ### Graceful.clear() 156 | 157 | Unsubscribe all `exit` listeners. 158 | 159 | ##### example 160 | ```javascript 161 | // add listener 162 | Graceful.on('exit', () => { 163 | console.log("Received some exit signal!"); 164 | return Promise.resolve("A promise to be waited on before dying"); 165 | }); 166 | 167 | Graceful.on('exit', (done) => { 168 | console.log("Another listener"); 169 | done(); 170 | }); 171 | 172 | // remove all listener 173 | Graceful.clear(); 174 | ``` 175 | 176 | ### Graceful.exit({Number} \[code], {String} \[signal]) 177 | 178 | Trigger graceful process exit. 179 | This method is meant to be a substitute command for `process.exit()` 180 | to allow other modules to exit gracefully in case of error. 181 | 182 | - `code` - (optional) exit code to be used. default - `process.exitCode` 183 | - `signal` - (optional) signal to be simulating for listeners. default - `SIGTERM` 184 | 185 | ##### example 186 | ```javascript 187 | 188 | server.listen(3333) 189 | .on('listening', function () { 190 | console.log('Yay!') 191 | }) 192 | .on('error', function (err) { 193 | if (err.code === 'EADDRINUSE') { 194 | console.error("Damn, Port is already in use..."); 195 | Graceful.exit(); 196 | } 197 | }); 198 | 199 | // exit code and signal can be specified 200 | // Graceful.exit(1); 201 | // Graceful.exit(1, 'SIGINT'); 202 | // Graceful.exit('SIGINT'); 203 | ``` 204 | 205 | ## Options 206 | 207 | Options are global and shared, any change will override previous values. 208 | 209 | #### Graceful.exitOnDouble = true {boolean} 210 | 211 | Whether to exit immediately when a second deadly event is received, 212 | For example when Ctrl-C is pressed twice etc.. 213 | When exiting due to double event, exit code will be `process.exitCode` or `1` (necessarily a non-zero) 214 | 215 | #### Graceful.timeout = 30000 {number} 216 | 217 | Maximum time to wait for exit listeners in `ms`. 218 | After exceeding the time, the process will force exit 219 | and the exit code will be `process.exitCode` or `1` (necessarily a non-zero) 220 | 221 | Setting the timeout to `0` will disable timeout functionality (will wait indefinitely) 222 | 223 | #### Graceful.captureExceptions = false {boolean} 224 | 225 | Whether to treat `uncaughtException` event as a terminating event and trigger graceful shutdown. 226 | 227 | ```typescript 228 | Graceful.captureExceptions = true; 229 | 230 | throw new Error('DANG!'); // this will now trigger graceful shutdown 231 | ``` 232 | 233 | #### Graceful.captureRejections = false {boolean} 234 | 235 | Whether to treat `unhandledRejection` event as a terminating event and trigger graceful shutdown. 236 | On newer `node` versions `unhandledRejection` is in-fact a terminating event 237 | 238 | ```typescript 239 | Graceful.captureRejections = true; 240 | 241 | Promise.reject(new Error('DANG!')); // this will now trigger graceful shutdown 242 | ``` 243 | 244 | #### exitCode 245 | 246 | `Graceful` will obey `process.exitCode` property value when exiting unless the exit is forced (double signal, timeout) in which case the exit code must be non-zero. 247 | --------------------------------------------------------------------------------