154 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			154 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | var RSVP = require('rsvp'); | ||
|  | 
 | ||
|  | var exit; | ||
|  | var handlers = []; | ||
|  | var lastTime; | ||
|  | var isExiting = false; | ||
|  | 
 | ||
|  | process.on('beforeExit', function (code) { | ||
|  |   if (handlers.length === 0) { return; } | ||
|  | 
 | ||
|  |   var own = lastTime = module.exports._flush(lastTime, code) | ||
|  |     .finally(function () { | ||
|  |       // if an onExit handler has called process.exit, do not disturb
 | ||
|  |       // `lastTime`.
 | ||
|  |       //
 | ||
|  |       // Otherwise, clear `lastTime` so that we know to synchronously call the
 | ||
|  |       // real `process.exit` with the given exit code, when our captured
 | ||
|  |       // `process.exit` is called during a `process.on('exit')` handler
 | ||
|  |       //
 | ||
|  |       // This is impossible to reason about, don't feel bad.  Just look at
 | ||
|  |       // test-natural-exit-subprocess-error.js
 | ||
|  |       if (own === lastTime) { | ||
|  |         lastTime = undefined; | ||
|  |       } | ||
|  |     }); | ||
|  | }); | ||
|  | 
 | ||
|  | // This exists only for testing
 | ||
|  | module.exports._reset = function () { | ||
|  |   module.exports.releaseExit(); | ||
|  |   handlers = []; | ||
|  |   lastTime = undefined; | ||
|  |   isExiting = false; | ||
|  |   firstExitCode = undefined; | ||
|  | } | ||
|  | 
 | ||
|  | /* | ||
|  |  * To allow cooperative async exit handlers, we unfortunately must hijack | ||
|  |  * process.exit. | ||
|  |  * | ||
|  |  * It allows a handler to ensure exit, without that exit handler impeding other | ||
|  |  * similar handlers | ||
|  |  * | ||
|  |  * for example, see: https://github.com/sindresorhus/ora/issues/27
 | ||
|  |  * | ||
|  |  */ | ||
|  | module.exports.releaseExit = function() { | ||
|  |   if (exit) { | ||
|  |     process.exit = exit; | ||
|  |     exit = null; | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | var firstExitCode; | ||
|  | 
 | ||
|  | module.exports.captureExit = function() { | ||
|  |   if (exit) { | ||
|  |     // already captured, no need to do more work
 | ||
|  |     return; | ||
|  |   } | ||
|  |   exit = process.exit; | ||
|  | 
 | ||
|  |   process.exit = function(code) { | ||
|  |     if (handlers.length === 0 && lastTime === undefined) { | ||
|  |       // synchronously exit.
 | ||
|  |       //
 | ||
|  |       // We do this brecause either
 | ||
|  |       //
 | ||
|  |       //  1.  The process exited due to a call to `process.exit` but we have no
 | ||
|  |       //      async work to do because no handlers had been attached.  It
 | ||
|  |       //      doesn't really matter whether we take this branch or not in this
 | ||
|  |       //      case.
 | ||
|  |       //
 | ||
|  |       //  2.  The process exited naturally.  We did our async work during
 | ||
|  |       //      `beforeExit` and are in this function because someone else has
 | ||
|  |       //      called `process.exit` during an `on('exit')` hook.  The only way
 | ||
|  |       //      for us to preserve the exit code in this case is to exit
 | ||
|  |       //      synchronously.
 | ||
|  |       //
 | ||
|  |       return exit.call(process, code); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (firstExitCode === undefined) { | ||
|  |       firstExitCode = code; | ||
|  |     } | ||
|  |     var own = lastTime = module.exports._flush(lastTime, firstExitCode) | ||
|  |       .then(function() { | ||
|  |         // if another chain has started, let it exit
 | ||
|  |         if (own !== lastTime) { return; } | ||
|  |         exit.call(process, firstExitCode); | ||
|  |       }) | ||
|  |       .catch(function(error) { | ||
|  |         // if another chain has started, let it exit
 | ||
|  |         if (own !== lastTime) { | ||
|  |           throw error; | ||
|  |         } | ||
|  |         console.error(error); | ||
|  |         exit.call(process, 1); | ||
|  |       }); | ||
|  |   }; | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports._handlers = handlers; | ||
|  | module.exports._flush = function(lastTime, code) { | ||
|  |   isExiting = true; | ||
|  |   var work = handlers.splice(0, handlers.length); | ||
|  | 
 | ||
|  |   return RSVP.Promise.resolve(lastTime). | ||
|  |     then(function() { | ||
|  |       var firstRejected; | ||
|  |       return RSVP.allSettled(work.map(function(handler) { | ||
|  |         return RSVP.resolve(handler.call(null, code)).catch(function(e) { | ||
|  |           if (!firstRejected) { | ||
|  |             firstRejected = e; | ||
|  |           } | ||
|  |           throw e; | ||
|  |         }); | ||
|  |       })).then(function(results) { | ||
|  |         if (firstRejected) { | ||
|  |           throw firstRejected; | ||
|  |         } | ||
|  |       }); | ||
|  |     }); | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports.onExit = function(cb) { | ||
|  |   if (!exit) { | ||
|  |     throw new Error('Cannot install handler when exit is not captured.  Call `captureExit()` first'); | ||
|  |   } | ||
|  |   if (isExiting) { | ||
|  |     throw new Error('Cannot install handler while `onExit` handlers are running.'); | ||
|  |   } | ||
|  |   var index = handlers.indexOf(cb); | ||
|  | 
 | ||
|  |   if (index > -1) { return; } | ||
|  |   handlers.push(cb); | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports.offExit = function(cb) { | ||
|  |   var index = handlers.indexOf(cb); | ||
|  | 
 | ||
|  |   if (index < 0) { return; } | ||
|  | 
 | ||
|  |   handlers.splice(index, 1); | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports.exit  = function() { | ||
|  |   exit.apply(process, arguments); | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports.listenerCount = function() { | ||
|  |   return handlers.length; | ||
|  | }; |