You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
154 lines
4.1 KiB
154 lines
4.1 KiB
4 years ago
|
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;
|
||
|
};
|