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.
Files changed (122) hide show
  1. package/.babelrc +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/README.cn.md +114 -3
  4. package/README.md +106 -5
  5. package/coverage/all-off.js.html +133 -0
  6. package/coverage/base.css +224 -0
  7. package/coverage/block-navigation.js +87 -0
  8. package/coverage/clover.xml +572 -0
  9. package/coverage/consts.js.html +154 -0
  10. package/coverage/default-methods.js.html +2068 -0
  11. package/coverage/event-emitter.js.html +121 -0
  12. package/coverage/event.js.html +286 -0
  13. package/coverage/eventable.js.html +355 -0
  14. package/coverage/favicon.png +0 -0
  15. package/coverage/has-listeners.js.html +166 -0
  16. package/coverage/index.html +281 -0
  17. package/coverage/lcov-report/all-off.js.html +133 -0
  18. package/coverage/lcov-report/base.css +224 -0
  19. package/coverage/lcov-report/block-navigation.js +87 -0
  20. package/coverage/lcov-report/consts.js.html +154 -0
  21. package/coverage/lcov-report/default-methods.js.html +2068 -0
  22. package/coverage/lcov-report/event-emitter.js.html +121 -0
  23. package/coverage/lcov-report/event.js.html +286 -0
  24. package/coverage/lcov-report/eventable.js.html +355 -0
  25. package/coverage/lcov-report/favicon.png +0 -0
  26. package/coverage/lcov-report/has-listeners.js.html +166 -0
  27. package/coverage/lcov-report/index.html +281 -0
  28. package/coverage/lcov-report/once-promise.js.html +325 -0
  29. package/coverage/lcov-report/pipe-async.js.html +385 -0
  30. package/coverage/lcov-report/pipe.js.html +244 -0
  31. package/coverage/lcov-report/prettify.css +1 -0
  32. package/coverage/lcov-report/prettify.js +2 -0
  33. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  34. package/coverage/lcov-report/sorter.js +210 -0
  35. package/coverage/lcov-report/unify.js.html +259 -0
  36. package/coverage/lcov-report/wrap-event-emitter.js.html +223 -0
  37. package/coverage/lcov.info +1201 -0
  38. package/coverage/once-promise.js.html +325 -0
  39. package/coverage/pipe-async.js.html +385 -0
  40. package/coverage/pipe.js.html +244 -0
  41. package/coverage/prettify.css +1 -0
  42. package/coverage/prettify.js +2 -0
  43. package/coverage/sort-arrow-sprite.png +0 -0
  44. package/coverage/sorter.js +210 -0
  45. package/coverage/unify.js.html +259 -0
  46. package/coverage/wrap-event-emitter.js.html +223 -0
  47. package/docs/README.md +106 -5
  48. package/docs/all-off/functions/allOff.md +1 -1
  49. package/docs/consts/README.md +4 -0
  50. package/docs/consts/functions/createAbortError.md +18 -0
  51. package/docs/consts/variables/RegExpEventSymbol.md +1 -1
  52. package/docs/consts/variables/states.md +2 -2
  53. package/docs/default-methods/functions/getEventableMethods.md +34 -18
  54. package/docs/event/classes/Event.md +23 -13
  55. package/docs/event-emitter/classes/EventEmitter-1.md +2 -2
  56. package/docs/event-emitter/classes/EventEmitter.md +28 -28
  57. package/docs/eventable/functions/eventable.md +1 -1
  58. package/docs/has-listeners/functions/hasListeners.md +2 -2
  59. package/docs/index/README.md +12 -0
  60. package/docs/modules.md +1 -0
  61. package/docs/once-promise/README.md +17 -0
  62. package/docs/once-promise/functions/oncePromise.md +60 -0
  63. package/docs/pipe/functions/pipe.md +3 -3
  64. package/docs/pipe-async/functions/pipeAsync.md +3 -3
  65. package/docs/unify/functions/unify.md +1 -1
  66. package/docs/util/array-remove/functions/remove.md +1 -1
  67. package/docs/util/object-for-each/functions/forEach.md +1 -1
  68. package/docs/util/string-pad/functions/pad.md +1 -1
  69. package/docs/util/to-int/functions/toInt.md +1 -1
  70. package/docs/util/valid-callable/functions/validCallable.md +1 -1
  71. package/docs/util/valid-object/functions/validObject.md +1 -1
  72. package/docs/wrap-event-emitter/functions/wrapEventEmitter.md +3 -3
  73. package/docs/wrap-event-emitter/variables/methods.md +1 -1
  74. package/lib/all-off.mjs +16 -0
  75. package/lib/consts.d.ts +7 -1
  76. package/lib/consts.js +17 -2
  77. package/lib/consts.mjs +23 -0
  78. package/lib/default-methods.d.ts +12 -4
  79. package/lib/default-methods.js +174 -22
  80. package/lib/default-methods.mjs +661 -0
  81. package/lib/event-emitter.mjs +12 -0
  82. package/lib/event.d.ts +10 -4
  83. package/lib/event.js +6 -0
  84. package/lib/event.mjs +67 -0
  85. package/lib/eventable.d.ts +1 -1
  86. package/lib/eventable.mjs +90 -0
  87. package/lib/has-listeners.d.ts +1 -1
  88. package/lib/has-listeners.mjs +27 -0
  89. package/lib/index.d.ts +1 -0
  90. package/lib/index.js +12 -0
  91. package/lib/index.mjs +16 -0
  92. package/lib/once-promise.d.ts +23 -0
  93. package/lib/once-promise.js +82 -0
  94. package/lib/once-promise.mjs +80 -0
  95. package/lib/pipe-async.d.ts +4 -2
  96. package/lib/pipe-async.js +16 -3
  97. package/lib/pipe-async.mjs +100 -0
  98. package/lib/pipe.d.ts +1 -1
  99. package/lib/pipe.mjs +53 -0
  100. package/lib/unify.js +1 -1
  101. package/lib/unify.mjs +58 -0
  102. package/lib/util/array-remove.mjs +16 -0
  103. package/lib/util/object-for-each.mjs +20 -0
  104. package/lib/util/promise-any.mjs +45 -0
  105. package/lib/util/string-pad.mjs +19 -0
  106. package/lib/util/to-int.mjs +9 -0
  107. package/lib/util/valid-callable.mjs +6 -0
  108. package/lib/util/valid-object.mjs +8 -0
  109. package/lib/wrap-event-emitter.d.ts +1 -1
  110. package/lib/wrap-event-emitter.mjs +46 -0
  111. package/package.json +29 -20
  112. package/scripts/copy-to-mjs.mjs +79 -0
  113. package/src/consts.js +10 -0
  114. package/src/default-methods.js +169 -24
  115. package/src/event.js +6 -0
  116. package/src/index.js +1 -0
  117. package/src/once-promise.js +80 -0
  118. package/src/pipe-async.js +16 -3
  119. package/src/unify.js +1 -1
  120. package/tsconfig.json +2 -1
  121. package/vitest.config.js +61 -0
  122. 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;