index.js
4.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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;
};