events-ex 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +1 -1
- package/CHANGELOG.md +25 -0
- package/README.cn.md +114 -3
- package/README.md +106 -5
- package/coverage/all-off.js.html +133 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +572 -0
- package/coverage/consts.js.html +154 -0
- package/coverage/default-methods.js.html +2068 -0
- package/coverage/event-emitter.js.html +121 -0
- package/coverage/event.js.html +286 -0
- package/coverage/eventable.js.html +355 -0
- package/coverage/favicon.png +0 -0
- package/coverage/has-listeners.js.html +166 -0
- package/coverage/index.html +281 -0
- package/coverage/lcov-report/all-off.js.html +133 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/consts.js.html +154 -0
- package/coverage/lcov-report/default-methods.js.html +2068 -0
- package/coverage/lcov-report/event-emitter.js.html +121 -0
- package/coverage/lcov-report/event.js.html +286 -0
- package/coverage/lcov-report/eventable.js.html +355 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/has-listeners.js.html +166 -0
- package/coverage/lcov-report/index.html +281 -0
- package/coverage/lcov-report/once-promise.js.html +325 -0
- package/coverage/lcov-report/pipe-async.js.html +385 -0
- package/coverage/lcov-report/pipe.js.html +244 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/unify.js.html +259 -0
- package/coverage/lcov-report/wrap-event-emitter.js.html +223 -0
- package/coverage/lcov.info +1201 -0
- package/coverage/once-promise.js.html +325 -0
- package/coverage/pipe-async.js.html +385 -0
- package/coverage/pipe.js.html +244 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/unify.js.html +259 -0
- package/coverage/wrap-event-emitter.js.html +223 -0
- package/docs/README.md +106 -5
- package/docs/all-off/functions/allOff.md +1 -1
- package/docs/consts/README.md +4 -0
- package/docs/consts/functions/createAbortError.md +18 -0
- package/docs/consts/variables/RegExpEventSymbol.md +1 -1
- package/docs/consts/variables/states.md +2 -2
- package/docs/default-methods/functions/getEventableMethods.md +34 -18
- package/docs/event/classes/Event.md +23 -13
- package/docs/event-emitter/classes/EventEmitter-1.md +2 -2
- package/docs/event-emitter/classes/EventEmitter.md +28 -28
- package/docs/eventable/functions/eventable.md +1 -1
- package/docs/has-listeners/functions/hasListeners.md +2 -2
- package/docs/index/README.md +12 -0
- package/docs/modules.md +1 -0
- package/docs/once-promise/README.md +17 -0
- package/docs/once-promise/functions/oncePromise.md +60 -0
- package/docs/pipe/functions/pipe.md +3 -3
- package/docs/pipe-async/functions/pipeAsync.md +3 -3
- package/docs/unify/functions/unify.md +1 -1
- package/docs/util/array-remove/functions/remove.md +1 -1
- package/docs/util/object-for-each/functions/forEach.md +1 -1
- package/docs/util/string-pad/functions/pad.md +1 -1
- package/docs/util/to-int/functions/toInt.md +1 -1
- package/docs/util/valid-callable/functions/validCallable.md +1 -1
- package/docs/util/valid-object/functions/validObject.md +1 -1
- package/docs/wrap-event-emitter/functions/wrapEventEmitter.md +3 -3
- package/docs/wrap-event-emitter/variables/methods.md +1 -1
- package/lib/all-off.mjs +16 -0
- package/lib/consts.d.ts +7 -1
- package/lib/consts.js +17 -2
- package/lib/consts.mjs +23 -0
- package/lib/default-methods.d.ts +12 -4
- package/lib/default-methods.js +174 -22
- package/lib/default-methods.mjs +661 -0
- package/lib/event-emitter.mjs +12 -0
- package/lib/event.d.ts +10 -4
- package/lib/event.js +6 -0
- package/lib/event.mjs +67 -0
- package/lib/eventable.d.ts +1 -1
- package/lib/eventable.mjs +90 -0
- package/lib/has-listeners.d.ts +1 -1
- package/lib/has-listeners.mjs +27 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +12 -0
- package/lib/index.mjs +16 -0
- package/lib/once-promise.d.ts +23 -0
- package/lib/once-promise.js +82 -0
- package/lib/once-promise.mjs +80 -0
- package/lib/pipe-async.d.ts +4 -2
- package/lib/pipe-async.js +16 -3
- package/lib/pipe-async.mjs +100 -0
- package/lib/pipe.d.ts +1 -1
- package/lib/pipe.mjs +53 -0
- package/lib/unify.js +1 -1
- package/lib/unify.mjs +58 -0
- package/lib/util/array-remove.mjs +16 -0
- package/lib/util/object-for-each.mjs +20 -0
- package/lib/util/promise-any.mjs +45 -0
- package/lib/util/string-pad.mjs +19 -0
- package/lib/util/to-int.mjs +9 -0
- package/lib/util/valid-callable.mjs +6 -0
- package/lib/util/valid-object.mjs +8 -0
- package/lib/wrap-event-emitter.d.ts +1 -1
- package/lib/wrap-event-emitter.mjs +46 -0
- package/package.json +29 -20
- package/scripts/copy-to-mjs.mjs +79 -0
- package/src/consts.js +10 -0
- package/src/default-methods.js +169 -24
- package/src/event.js +6 -0
- package/src/index.js +1 -0
- package/src/once-promise.js +80 -0
- package/src/pipe-async.js +16 -3
- package/src/unify.js +1 -1
- package/tsconfig.json +2 -1
- package/vitest.config.js +61 -0
- package/.mocharc.yml +0 -3
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
import {defineProperty, isArray, isFunction, isNumber, isObject, isRegExp as _isRegExp, isUndefined, isRegExpStr, toRegExp } from 'util-ex'
|
|
2
|
+
import './util/promise-any'
|
|
3
|
+
import {RegExpEventSymbol, createAbortError} from './consts.mjs'
|
|
4
|
+
import {Event} from './event.mjs';
|
|
5
|
+
|
|
6
|
+
const create = Object.create
|
|
7
|
+
const UnCAUGHT_ERR = "Uncaught, unspecified 'error' event."
|
|
8
|
+
const slice = Array.prototype.slice
|
|
9
|
+
|
|
10
|
+
function isRegExp(value) {
|
|
11
|
+
const result = isRegExpStr(value)
|
|
12
|
+
return result || _isRegExp(value)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getEventableMethods(aClass) {
|
|
16
|
+
return {
|
|
17
|
+
/**
|
|
18
|
+
* Configures the event emitter with specified options using a Fluent API.
|
|
19
|
+
* @param {Object} options - Configuration options for event emission.
|
|
20
|
+
* @param {string} [options.asyncMode='serial'] - The mode of asynchronous emission ('serial' or 'parallel').
|
|
21
|
+
* @param {string} [options.resultMode='last'] - The strategy for handling multiple return values ('last', 'first', 'collect').
|
|
22
|
+
* @param {AbortSignal} [options.signal] - An AbortSignal to cancel async event emission.
|
|
23
|
+
* @param {boolean|null} [options.raiseError] - Controls error handling behavior:
|
|
24
|
+
* - `true`: Always throw listener errors immediately.
|
|
25
|
+
* - `false`: Silently swallow listener errors.
|
|
26
|
+
* - `null`: Throw only for 'error' events with no error listeners (Node.js default).
|
|
27
|
+
* - `undefined` (default): Same as `false` for emitAsync.
|
|
28
|
+
* @returns {import('./event-emitter').EventEmitter} A proxy object representing the configured EventEmitter.
|
|
29
|
+
*/
|
|
30
|
+
configure(options) {
|
|
31
|
+
const proxy = Object.create(this)
|
|
32
|
+
const mergedOptions = Object.assign({}, this._eeRuntimeOptions, options)
|
|
33
|
+
defineProperty(proxy, '_eeRuntimeOptions', mergedOptions)
|
|
34
|
+
return proxy
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A shortcut for parallel configuration.
|
|
39
|
+
* @param {string} [resultMode='last'] - The strategy for handling multiple return values.
|
|
40
|
+
* @returns {import('./event-emitter').EventEmitter} A proxy object representing the configured EventEmitter.
|
|
41
|
+
*/
|
|
42
|
+
parallel(resultMode) {
|
|
43
|
+
return this.configure({asyncMode: 'parallel', resultMode: resultMode || 'last'})
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Sets the configuration options for the EventEmitter instance.
|
|
48
|
+
* @param {Object} options - Configuration options for the emitter (e.g., asyncMode, resultMode, maxListeners, raiseError).
|
|
49
|
+
* @returns {import('./event-emitter').EventEmitter} The EventEmitter instance for chaining.
|
|
50
|
+
*/
|
|
51
|
+
setEmitterOptions(options) {
|
|
52
|
+
if (!isObject(options)) {return this}
|
|
53
|
+
let data
|
|
54
|
+
if (!this.hasOwnProperty('_emitterOptions')) {
|
|
55
|
+
data = create(null)
|
|
56
|
+
defineProperty(this, '_emitterOptions', data)
|
|
57
|
+
} else {
|
|
58
|
+
data = this._emitterOptions
|
|
59
|
+
}
|
|
60
|
+
Object.assign(data, options)
|
|
61
|
+
if (!isUndefined(options.maxListeners) && isFunction(this.setMaxListeners)) {
|
|
62
|
+
this.setMaxListeners(options.maxListeners)
|
|
63
|
+
}
|
|
64
|
+
return this
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Adds a listener function to the specified event type.
|
|
69
|
+
* @param {string|RegExp} type - The event type to listen for.
|
|
70
|
+
* @param {Function} listener - The listener function to be called when the event is emitted.
|
|
71
|
+
* @param {number|'first'|'last'} [index] - The index at which to insert the listener.
|
|
72
|
+
* - 'first' or -Infinity: Adds to the "Head" zone. The first listener added as 'first' is placed at the very front.
|
|
73
|
+
* - 'last' or Infinity: Adds to the "Tail" zone. The first listener added as 'last' will always be the very last one to execute.
|
|
74
|
+
* - number: Inserts at the specified index within the "Body" (normal) zone.
|
|
75
|
+
* If not specified, the listener is added to the end of the "Body" zone.
|
|
76
|
+
* @returns {import('./event-emitter').EventEmitter} The EventEmitter instance to allow chaining.
|
|
77
|
+
* @throws {TypeError} If the listener is not a function.
|
|
78
|
+
*/
|
|
79
|
+
on(type, listener, index) {
|
|
80
|
+
if (!isFunction(listener)) {throw new TypeError(listener + ' is not a function')}
|
|
81
|
+
let data
|
|
82
|
+
if (!this.hasOwnProperty('_events')) {
|
|
83
|
+
data = create(null)
|
|
84
|
+
defineProperty(this, '_events', data)
|
|
85
|
+
} else {
|
|
86
|
+
data = this._events
|
|
87
|
+
}
|
|
88
|
+
// To avoid recursion in the case that type === 'newListener'! Before
|
|
89
|
+
// adding it to the listeners, first emit 'newListener'.
|
|
90
|
+
if (data.newListener) {
|
|
91
|
+
this.emit('newListener', type, isFunction(listener.listener)? listener.listener:listener)
|
|
92
|
+
}
|
|
93
|
+
if (isRegExp(type)) {
|
|
94
|
+
data = data[RegExpEventSymbol] || (data[RegExpEventSymbol] = create(null))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const isFirst = index === -Infinity || index === 'first'
|
|
98
|
+
const isLast = index === Infinity || index === 'last'
|
|
99
|
+
|
|
100
|
+
if (!data[type]) {
|
|
101
|
+
if (isFirst || isLast || typeof index === 'number') {
|
|
102
|
+
data[type] = [listener]
|
|
103
|
+
if (isFirst) data[type]._headCount = 1
|
|
104
|
+
if (isLast) data[type]._tailCount = 1
|
|
105
|
+
} else {
|
|
106
|
+
data[type] = listener
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
if (isFunction(data[type])) {
|
|
110
|
+
data[type] = [data[type]]
|
|
111
|
+
}
|
|
112
|
+
const listeners = data[type]
|
|
113
|
+
const headCount = listeners._headCount || 0
|
|
114
|
+
const tailCount = listeners._tailCount || 0
|
|
115
|
+
|
|
116
|
+
if (isFirst) {
|
|
117
|
+
listeners.splice(headCount, 0, listener)
|
|
118
|
+
listeners._headCount = headCount + 1
|
|
119
|
+
} else if (isLast) {
|
|
120
|
+
listeners.splice(listeners.length - tailCount, 0, listener)
|
|
121
|
+
listeners._tailCount = tailCount + 1
|
|
122
|
+
} else if (typeof index === 'number' && !isNaN(index)) {
|
|
123
|
+
const pos = Math.min(Math.max(headCount + index, headCount), listeners.length - tailCount)
|
|
124
|
+
listeners.splice(pos, 0, listener)
|
|
125
|
+
} else {
|
|
126
|
+
listeners.splice(listeners.length - tailCount, 0, listener)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Check for listener leak
|
|
130
|
+
if (isObject(data[type]) && !data[type].warned) {
|
|
131
|
+
let m
|
|
132
|
+
if (!isUndefined(this._maxListeners))
|
|
133
|
+
m = this._maxListeners
|
|
134
|
+
else {
|
|
135
|
+
m = aClass.defaultMaxListeners
|
|
136
|
+
}
|
|
137
|
+
if (m && m > 0 && data[type].length > m) {
|
|
138
|
+
data[type].warned = true
|
|
139
|
+
console.error('(node) warning: possible EventEmitter memory ' +
|
|
140
|
+
'leak detected. %d %s listeners added. ' +
|
|
141
|
+
'Use emitter.setMaxListeners() to increase limit.',
|
|
142
|
+
data[type].length, type)
|
|
143
|
+
// eslint-disable-next-line no-console
|
|
144
|
+
console.trace()
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return this
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Adds a one-time listener function to the specified event type.
|
|
152
|
+
* @param {string|RegExp} type - The event type to listen for.
|
|
153
|
+
* @param {Function} listener - The listener function to be called once when the event is emitted.
|
|
154
|
+
* @param {number|'first'|'last'} [index] - The index at which to insert the listener.
|
|
155
|
+
* - 'first' or -Infinity: Adds to the "Head" zone. The first listener added as 'first' is placed at the very front.
|
|
156
|
+
* - 'last' or Infinity: Adds to the "Tail" zone. The first listener added as 'last' will always be the very last one to execute.
|
|
157
|
+
* - number: Inserts at the specified index within the "Body" (normal) zone.
|
|
158
|
+
* If not specified, the listener is added to the end of the "Body" zone.
|
|
159
|
+
* @returns {import('./event-emitter').EventEmitter} The EventEmitter instance to allow chaining.
|
|
160
|
+
* @throws {TypeError} If the listener is not a function.
|
|
161
|
+
*/
|
|
162
|
+
once(type, listener, index) {
|
|
163
|
+
if (!isFunction(listener)) {throw new TypeError(listener + ' is not a function' )}
|
|
164
|
+
let fired = false
|
|
165
|
+
const self = this
|
|
166
|
+
|
|
167
|
+
function _once() {
|
|
168
|
+
self.off(type, _once)
|
|
169
|
+
if (!fired) {
|
|
170
|
+
fired = true
|
|
171
|
+
const result = listener.apply(this, arguments)
|
|
172
|
+
if (result instanceof Promise) {
|
|
173
|
+
return result
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
_once.listener = listener
|
|
178
|
+
this.on(type, _once, index)
|
|
179
|
+
return this
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Emits the specified event type with the given arguments.
|
|
185
|
+
* @param {...*} args - The event type followed by any number of arguments to be passed to the listener functions.
|
|
186
|
+
* @returns {*} The result of the event.
|
|
187
|
+
*/
|
|
188
|
+
emit(/* type, msg , ... */) {
|
|
189
|
+
const r = _emit.apply(this, arguments)
|
|
190
|
+
if (!r) {return}
|
|
191
|
+
const args = r.args
|
|
192
|
+
const listeners = r.listeners
|
|
193
|
+
const evt = Event(this, r.type)
|
|
194
|
+
const errs = []
|
|
195
|
+
const opts = _getOptions(this)
|
|
196
|
+
let _throwErr
|
|
197
|
+
try {
|
|
198
|
+
let i = 0
|
|
199
|
+
let listener
|
|
200
|
+
while (listener = listeners[i]){
|
|
201
|
+
try {
|
|
202
|
+
_notify(listener, evt, args)
|
|
203
|
+
if (evt.stopped) {break}
|
|
204
|
+
} catch(err) {
|
|
205
|
+
if (opts.raiseError === true) {throw err}
|
|
206
|
+
errs.push({err: err, listener: listener})
|
|
207
|
+
}
|
|
208
|
+
++i
|
|
209
|
+
}
|
|
210
|
+
if (errs.length) {
|
|
211
|
+
for (let i=0;i<errs.length;i++) {
|
|
212
|
+
const it = errs[i]
|
|
213
|
+
if (r.type === 'error') {throw it.err}
|
|
214
|
+
this.emit('error', it.err, 'notify', r.type, it.listener, args)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} finally {
|
|
218
|
+
if (r.type === 'error' && opts.raiseError === null) {
|
|
219
|
+
const err = args[0]
|
|
220
|
+
_throwErr = err instanceof Error ? err : new Error(UnCAUGHT_ERR)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (_throwErr) throw _throwErr
|
|
224
|
+
return evt.end()
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Asynchronously emits the specified event type with the given arguments.
|
|
229
|
+
* @param {...*} args - The event type followed by any number of arguments to be passed to the listener functions.
|
|
230
|
+
* @returns {Promise<*>} A promise that resolves with the result of the event.
|
|
231
|
+
*/
|
|
232
|
+
async emitAsync(/* type, msg , ... */) {
|
|
233
|
+
const options = Object.assign({}, this._emitterOptions, this._eeRuntimeOptions)
|
|
234
|
+
if (options.signal && options.signal.aborted) {
|
|
235
|
+
throw createAbortError()
|
|
236
|
+
}
|
|
237
|
+
const r = _emit.apply(this, arguments)
|
|
238
|
+
if (!r) {return}
|
|
239
|
+
const args = r.args
|
|
240
|
+
const listeners = r.listeners
|
|
241
|
+
const evt = Event(this, r.type)
|
|
242
|
+
let _throwErr
|
|
243
|
+
try {
|
|
244
|
+
await _executeAsync.call(this, listeners, evt, args, options)
|
|
245
|
+
} catch (err) {
|
|
246
|
+
if (err && err.name === 'AbortError') throw err
|
|
247
|
+
if (options.raiseError === true) throw err
|
|
248
|
+
if (r.type === 'error') {
|
|
249
|
+
Object.defineProperty(err, '_errorInError', { value: true, enumerable: false })
|
|
250
|
+
throw err
|
|
251
|
+
}
|
|
252
|
+
if (err && err._errorInError) throw err
|
|
253
|
+
// Other unexpected errors: still return the event result
|
|
254
|
+
return evt.end()
|
|
255
|
+
}
|
|
256
|
+
if (r.type === 'error' && options.raiseError === null) {
|
|
257
|
+
const err = args[0]
|
|
258
|
+
_throwErr = err instanceof Error ? err : new Error(UnCAUGHT_ERR)
|
|
259
|
+
}
|
|
260
|
+
if (_throwErr) throw _throwErr
|
|
261
|
+
return evt.end()
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
setMaxListeners(n) {
|
|
265
|
+
if (!isNumber(n) || n < 0 || isNaN(n)) {throw new TypeError('n must be a positive number')}
|
|
266
|
+
if (!this.hasOwnProperty('_maxListeners')) {
|
|
267
|
+
defineProperty(this, '_maxListeners', n)
|
|
268
|
+
} else {
|
|
269
|
+
this._maxListeners = n
|
|
270
|
+
}
|
|
271
|
+
return this
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
listeners(type) {
|
|
275
|
+
let data = this._events
|
|
276
|
+
let result = []
|
|
277
|
+
if (data) {
|
|
278
|
+
if (isRegExp(type) && data[RegExpEventSymbol]) {
|
|
279
|
+
data = data[RegExpEventSymbol]
|
|
280
|
+
}
|
|
281
|
+
const listener = data[type]
|
|
282
|
+
if (listener) {
|
|
283
|
+
if (isFunction(listener)) {
|
|
284
|
+
result = [listener]
|
|
285
|
+
} else {
|
|
286
|
+
result = listener.slice()
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return result
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
listenerCount(emitter, type) {
|
|
294
|
+
if (typeof emitter === 'string' || _isRegExp(emitter))
|
|
295
|
+
type = emitter
|
|
296
|
+
emitter = this
|
|
297
|
+
let data = emitter._events
|
|
298
|
+
let result = 0
|
|
299
|
+
if (data) {
|
|
300
|
+
if (isRegExp(type) && data[RegExpEventSymbol]) {
|
|
301
|
+
data = data[RegExpEventSymbol]
|
|
302
|
+
}
|
|
303
|
+
const listener = data[type]
|
|
304
|
+
if (listener) {
|
|
305
|
+
if (isFunction(listener)) {
|
|
306
|
+
result = 1
|
|
307
|
+
} else {
|
|
308
|
+
result = listener.length
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return result
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Removes a listener function from the specified event type.
|
|
317
|
+
* @param {string|RegExp} type - The event type to remove the listener from.
|
|
318
|
+
* @param {Function} listener - The listener function to be removed.
|
|
319
|
+
* @returns {import('./event-emitter').EventEmitter} The EventEmitter instance to allow chaining.
|
|
320
|
+
* @throws {TypeError} If the listener is not a function.
|
|
321
|
+
*/
|
|
322
|
+
off(type, listener) {
|
|
323
|
+
if (!isFunction(listener)) {throw new TypeError(listener + ' is not a function')}
|
|
324
|
+
if (!this.hasOwnProperty('_events')) {return this}
|
|
325
|
+
let data = this._events
|
|
326
|
+
const hasRemover = data.removeListener
|
|
327
|
+
if (isRegExp(type) && data[RegExpEventSymbol]) {
|
|
328
|
+
data = data[RegExpEventSymbol]
|
|
329
|
+
}
|
|
330
|
+
if (!data[type]) {return this}
|
|
331
|
+
const listeners = data[type]
|
|
332
|
+
if ((listeners === listener) || (listeners.listener === listener)) {
|
|
333
|
+
delete data[type]
|
|
334
|
+
if (hasRemover) {this.emit('removeListener', type, listener)}
|
|
335
|
+
} else if (isObject(listeners)) {
|
|
336
|
+
let i = listeners.length
|
|
337
|
+
while (--i >= 0) {
|
|
338
|
+
const candidate = listeners[i]
|
|
339
|
+
if (candidate === listener || candidate.listener === listener) {break}
|
|
340
|
+
}
|
|
341
|
+
if (i < 0) {return this}
|
|
342
|
+
|
|
343
|
+
if (listeners._headCount && i < listeners._headCount) {
|
|
344
|
+
listeners._headCount--
|
|
345
|
+
} else if (listeners._tailCount && i >= listeners.length - listeners._tailCount) {
|
|
346
|
+
listeners._tailCount--
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (listeners.length === 1) {
|
|
350
|
+
delete data[type]
|
|
351
|
+
} else if (listeners.length === 2 && !listeners._headCount && !listeners._tailCount) {
|
|
352
|
+
data[type] = listeners[(i ? 0 : 1)]
|
|
353
|
+
listeners.length = 1
|
|
354
|
+
} else {
|
|
355
|
+
listeners.splice(i, 1)
|
|
356
|
+
}
|
|
357
|
+
if (hasRemover) {this.emit('removeListener', type, listener)}
|
|
358
|
+
}
|
|
359
|
+
return this
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Removes all listener functions from the specified event type.
|
|
364
|
+
* @param {string|RegExp} type - The event type to remove the listener from.
|
|
365
|
+
* @returns {import('./event-emitter').EventEmitter} The EventEmitter instance to allow chaining.
|
|
366
|
+
* @throws {TypeError} If the listener is not a function.
|
|
367
|
+
*/
|
|
368
|
+
removeAllListeners(type) {
|
|
369
|
+
if (!this.hasOwnProperty('_events')) {return this}
|
|
370
|
+
let data = this._events
|
|
371
|
+
const regExpEvents = data[RegExpEventSymbol]
|
|
372
|
+
// not listening for removeListener, no need to emit
|
|
373
|
+
if (!data.removeListener) {
|
|
374
|
+
if (type == null){
|
|
375
|
+
// delete this._events
|
|
376
|
+
Object.keys(data).forEach(key => delete data[key])
|
|
377
|
+
if (regExpEvents) {delete data[RegExpEventSymbol]}
|
|
378
|
+
} else {
|
|
379
|
+
if (isRegExp(type) && regExpEvents) {
|
|
380
|
+
data = regExpEvents
|
|
381
|
+
}
|
|
382
|
+
delete data[type]
|
|
383
|
+
}
|
|
384
|
+
return this
|
|
385
|
+
}
|
|
386
|
+
// emit removeListener for all listeners on all events
|
|
387
|
+
if (type == null) {
|
|
388
|
+
for (const key in data) {
|
|
389
|
+
if (key === 'removeListener' || key === RegExpEventSymbol) {continue}
|
|
390
|
+
this.removeAllListeners(key)
|
|
391
|
+
}
|
|
392
|
+
if (regExpEvents) {
|
|
393
|
+
for (const key in regExpEvents) {
|
|
394
|
+
this.removeAllListeners(toRegExp(key))
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
this.removeAllListeners('removeListener')
|
|
398
|
+
// delete this._events
|
|
399
|
+
// Object.keys(data).forEach(key => delete data[key])
|
|
400
|
+
if (regExpEvents) {delete data[RegExpEventSymbol]}
|
|
401
|
+
return this
|
|
402
|
+
}
|
|
403
|
+
if ( isRegExp(type) && regExpEvents) {
|
|
404
|
+
data = regExpEvents
|
|
405
|
+
}
|
|
406
|
+
const listeners = data[type]
|
|
407
|
+
if (isFunction(listeners)){
|
|
408
|
+
this.off(type, listeners)
|
|
409
|
+
} else if (isArray(listeners)) {
|
|
410
|
+
// LIFO order
|
|
411
|
+
while (listeners.length && data[type]) {
|
|
412
|
+
this.off(type, listeners[listeners.length-1])
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
delete data[type]
|
|
416
|
+
return this
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export default getEventableMethods
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Merges instance-level emitter options with per-call runtime options.
|
|
425
|
+
* Runtime options (set via configure()) take precedence.
|
|
426
|
+
* @param {Object} self - The emitter instance.
|
|
427
|
+
* @returns {Object} Merged options.
|
|
428
|
+
*/
|
|
429
|
+
function _getOptions(self) {
|
|
430
|
+
return Object.assign({}, self._emitterOptions, self._eeRuntimeOptions)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function _emit(type, msg) {
|
|
434
|
+
const data = this._events
|
|
435
|
+
let listeners
|
|
436
|
+
if (data) {listeners = data[type]}
|
|
437
|
+
const args = slice.call(arguments, 1)
|
|
438
|
+
if (type === 'error' && !msg) {
|
|
439
|
+
msg = new Error(UnCAUGHT_ERR)
|
|
440
|
+
if (args.length > 0) {
|
|
441
|
+
args[0] = msg
|
|
442
|
+
} else {
|
|
443
|
+
args.push(msg)
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (type === 'error') {
|
|
447
|
+
const opts = _getOptions(this)
|
|
448
|
+
// raiseError: true → always throw, bypass listener dispatch
|
|
449
|
+
if (opts.raiseError === true) {
|
|
450
|
+
/* v8 ignore next — msg is guaranteed truthy here; falsy case is caught by earlier !msg check */
|
|
451
|
+
if (!(msg instanceof Error)) {msg = new Error(msg ? UnCAUGHT_ERR + msg : UnCAUGHT_ERR)}
|
|
452
|
+
throw msg
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// If there is no 'error' event listener then throw.
|
|
456
|
+
if (!listeners && type === 'error') {
|
|
457
|
+
const opts = _getOptions(this)
|
|
458
|
+
// raiseError: false → silently return, never throw
|
|
459
|
+
if (opts.raiseError === false) {
|
|
460
|
+
return
|
|
461
|
+
}
|
|
462
|
+
// raiseError: undefined / null → Node.js default behavior
|
|
463
|
+
/* v8 ignore next — msg is guaranteed truthy here; falsy case is caught by earlier !msg check */
|
|
464
|
+
if (!(msg instanceof Error)) {msg = new Error(msg ? UnCAUGHT_ERR + msg : UnCAUGHT_ERR)}
|
|
465
|
+
throw msg
|
|
466
|
+
}
|
|
467
|
+
const regExpEvents = data && data[RegExpEventSymbol]
|
|
468
|
+
if (regExpEvents) {
|
|
469
|
+
const matched = []
|
|
470
|
+
for (let key in regExpEvents) {
|
|
471
|
+
key = toRegExp(key)
|
|
472
|
+
if (key && key.test(type)) {
|
|
473
|
+
const listener = regExpEvents[key]
|
|
474
|
+
if (isArray(listener)) {
|
|
475
|
+
matched.push.apply(matched, listener)
|
|
476
|
+
} else {
|
|
477
|
+
matched.push(listener)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (matched.length) {
|
|
482
|
+
listeners = listeners ? !isObject(listeners) ? [listeners].concat(matched) : listeners.concat(matched) : matched
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (!listeners) {return}
|
|
486
|
+
if (!isObject(listeners)) {
|
|
487
|
+
listeners = [listeners]
|
|
488
|
+
} else {
|
|
489
|
+
listeners = listeners.slice()
|
|
490
|
+
}
|
|
491
|
+
return {type, args, listeners}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function _notify(listener, evt, args) {
|
|
495
|
+
let result
|
|
496
|
+
switch (args.length) {
|
|
497
|
+
case 0:
|
|
498
|
+
result = listener.call(evt)
|
|
499
|
+
break
|
|
500
|
+
case 1:
|
|
501
|
+
result = listener.call(evt, args[0])
|
|
502
|
+
break
|
|
503
|
+
case 2:
|
|
504
|
+
result = listener.call(evt, args[0], args[1])
|
|
505
|
+
break
|
|
506
|
+
default: {
|
|
507
|
+
result = listener.apply(evt, args)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return result
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Creates a promise that rejects with AbortError when the given AbortSignal fires.
|
|
515
|
+
* Sets evt.aborted = true before rejecting.
|
|
516
|
+
* @param {AbortSignal} signal - The abort signal to listen on.
|
|
517
|
+
* @param {import('./event').Event} evt - The event object to mark as aborted.
|
|
518
|
+
* @returns {Promise<never>} A promise that rejects with AbortError on abort.
|
|
519
|
+
*/
|
|
520
|
+
function _createAbortPromise(signal, evt) {
|
|
521
|
+
return new Promise((_, reject) => {
|
|
522
|
+
if (signal.aborted) {
|
|
523
|
+
evt.aborted = true
|
|
524
|
+
reject(createAbortError())
|
|
525
|
+
return
|
|
526
|
+
}
|
|
527
|
+
const onAbort = () => {
|
|
528
|
+
evt.aborted = true
|
|
529
|
+
reject(createAbortError())
|
|
530
|
+
}
|
|
531
|
+
signal.addEventListener('abort', onAbort, { once: true })
|
|
532
|
+
})
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Executes all listener promises in parallel and assigns the result to evt.
|
|
537
|
+
* @param {Promise[]} promises - The listener promises to execute.
|
|
538
|
+
* @param {import('./event').Event} evt - The event object.
|
|
539
|
+
* @param {string} resultMode - 'last', 'first', or 'collect'.
|
|
540
|
+
*/
|
|
541
|
+
async function _runParallelListeners(promises, evt, resultMode) {
|
|
542
|
+
if (resultMode === 'collect') {
|
|
543
|
+
const wrapped = promises.map(p => p.catch(() => undefined))
|
|
544
|
+
const results = await Promise.all(wrapped)
|
|
545
|
+
evt.result = results
|
|
546
|
+
} else if (resultMode === 'first') {
|
|
547
|
+
try {
|
|
548
|
+
await Promise.any(promises.map(p => p.then(res => res === undefined ? Promise.reject() : res)))
|
|
549
|
+
} catch (e) {
|
|
550
|
+
// If all rejected or returned undefined, ignore
|
|
551
|
+
}
|
|
552
|
+
} else {
|
|
553
|
+
await Promise.all(promises.map(p => p.catch(() => undefined)))
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
async function _executeAsync(listeners, evt, args, options) {
|
|
558
|
+
const asyncMode = options.asyncMode || 'serial'
|
|
559
|
+
const resultMode = options.resultMode || 'last'
|
|
560
|
+
const signal = options.signal
|
|
561
|
+
const errs = []
|
|
562
|
+
|
|
563
|
+
if (resultMode === 'collect') {
|
|
564
|
+
evt.result = []
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const notifyListener = async (listener) => {
|
|
568
|
+
try {
|
|
569
|
+
const result = await _notify(listener, evt, args)
|
|
570
|
+
if (result !== undefined) {
|
|
571
|
+
if (resultMode === 'first' && !evt.resolved) {
|
|
572
|
+
evt.result = result
|
|
573
|
+
evt.resolved = true
|
|
574
|
+
} else if (resultMode === 'last') {
|
|
575
|
+
evt.result = result
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return result
|
|
579
|
+
} catch (err) {
|
|
580
|
+
if (options.raiseError === true) {throw err}
|
|
581
|
+
errs.push({err, listener})
|
|
582
|
+
throw err
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (asyncMode === 'parallel') {
|
|
587
|
+
const promises = listeners.map(listener => notifyListener(listener))
|
|
588
|
+
|
|
589
|
+
if (options.raiseError === true) {
|
|
590
|
+
// Collect all rejections into AggregateError (all parallel listeners start simultaneously)
|
|
591
|
+
const settled = signal
|
|
592
|
+
? await Promise.race([
|
|
593
|
+
Promise.allSettled(promises),
|
|
594
|
+
_createAbortPromise(signal, evt)
|
|
595
|
+
])
|
|
596
|
+
: await Promise.allSettled(promises)
|
|
597
|
+
const rejections = settled.filter(r => r.status === 'rejected').map(r => r.reason)
|
|
598
|
+
if (rejections.length > 0) {
|
|
599
|
+
throw rejections.length === 1 ? rejections[0] : new AggregateError(rejections)
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
const runParallel = () => _runParallelListeners(promises, evt, resultMode)
|
|
603
|
+
|
|
604
|
+
if (signal) {
|
|
605
|
+
await Promise.race([runParallel(), _createAbortPromise(signal, evt)])
|
|
606
|
+
} else {
|
|
607
|
+
await runParallel()
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
// Serial mode
|
|
612
|
+
// Only race against abort signal when raiseError is explicitly set;
|
|
613
|
+
// default (undefined) keeps old behavior: check signal between listeners only.
|
|
614
|
+
const canRaceAbort = signal && 'raiseError' in options
|
|
615
|
+
for (const listener of listeners) {
|
|
616
|
+
// Check AbortSignal before firing the next listener
|
|
617
|
+
if (signal && signal.aborted) {
|
|
618
|
+
evt.aborted = true
|
|
619
|
+
break
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
try {
|
|
623
|
+
const result = canRaceAbort
|
|
624
|
+
? await Promise.race([notifyListener(listener), _createAbortPromise(signal, evt)])
|
|
625
|
+
: await notifyListener(listener)
|
|
626
|
+
if (resultMode === 'collect') {
|
|
627
|
+
evt.result.push(result)
|
|
628
|
+
}
|
|
629
|
+
if (evt.stopped || (resultMode === 'first' && evt.resolved)) break
|
|
630
|
+
} catch (err) {
|
|
631
|
+
if (canRaceAbort && err && err.name === 'AbortError') {
|
|
632
|
+
evt.aborted = true
|
|
633
|
+
throw err
|
|
634
|
+
}
|
|
635
|
+
if (options.raiseError === true) throw err
|
|
636
|
+
if (resultMode === 'collect') {
|
|
637
|
+
evt.result.push(undefined)
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (errs.length) {
|
|
644
|
+
for (let i = 0; i < errs.length; i++) {
|
|
645
|
+
const it = errs[i]
|
|
646
|
+
if (evt.type === 'error') {throw it.err}
|
|
647
|
+
// Temporarily clear signal so error re-emission is not blocked by an already-aborted signal
|
|
648
|
+
const savedSignal = this._eeRuntimeOptions && this._eeRuntimeOptions.signal
|
|
649
|
+
if (savedSignal) { delete this._eeRuntimeOptions.signal }
|
|
650
|
+
try {
|
|
651
|
+
await this.emitAsync('error', it.err, 'notify', evt.type, it.listener, args)
|
|
652
|
+
} finally {
|
|
653
|
+
if (savedSignal) { this._eeRuntimeOptions.signal = savedSignal }
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (evt.aborted) {
|
|
659
|
+
throw createAbortError()
|
|
660
|
+
}
|
|
661
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {defineProperty} from 'util-ex'
|
|
2
|
+
import {eventable} from './eventable.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @class
|
|
6
|
+
* @classdesc Class that represents an event emitter.
|
|
7
|
+
*/
|
|
8
|
+
export function EventEmitter() {defineProperty(this, '_events', {})}
|
|
9
|
+
|
|
10
|
+
eventable(EventEmitter);
|
|
11
|
+
|
|
12
|
+
export default EventEmitter;
|