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
package/src/default-methods.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {defineProperty, isArray, isFunction, isNumber, isObject, isRegExp as _isRegExp, isUndefined, isRegExpStr, toRegExp } from 'util-ex'
|
|
2
2
|
import './util/promise-any'
|
|
3
|
-
import {RegExpEventSymbol} from './consts'
|
|
3
|
+
import {RegExpEventSymbol, createAbortError} from './consts'
|
|
4
4
|
import {Event} from './event';
|
|
5
5
|
|
|
6
6
|
const create = Object.create
|
|
@@ -19,6 +19,12 @@ export function getEventableMethods(aClass) {
|
|
|
19
19
|
* @param {Object} options - Configuration options for event emission.
|
|
20
20
|
* @param {string} [options.asyncMode='serial'] - The mode of asynchronous emission ('serial' or 'parallel').
|
|
21
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.
|
|
22
28
|
* @returns {import('./event-emitter').EventEmitter} A proxy object representing the configured EventEmitter.
|
|
23
29
|
*/
|
|
24
30
|
configure(options) {
|
|
@@ -39,7 +45,7 @@ export function getEventableMethods(aClass) {
|
|
|
39
45
|
|
|
40
46
|
/**
|
|
41
47
|
* Sets the configuration options for the EventEmitter instance.
|
|
42
|
-
* @param {Object} options - Configuration options for the emitter (e.g., asyncMode, resultMode, maxListeners).
|
|
48
|
+
* @param {Object} options - Configuration options for the emitter (e.g., asyncMode, resultMode, maxListeners, raiseError).
|
|
43
49
|
* @returns {import('./event-emitter').EventEmitter} The EventEmitter instance for chaining.
|
|
44
50
|
*/
|
|
45
51
|
setEmitterOptions(options) {
|
|
@@ -62,7 +68,7 @@ export function getEventableMethods(aClass) {
|
|
|
62
68
|
* Adds a listener function to the specified event type.
|
|
63
69
|
* @param {string|RegExp} type - The event type to listen for.
|
|
64
70
|
* @param {Function} listener - The listener function to be called when the event is emitted.
|
|
65
|
-
* @param {number|'first'|'last'} [index] - The index at which to insert the listener.
|
|
71
|
+
* @param {number|'first'|'last'} [index] - The index at which to insert the listener.
|
|
66
72
|
* - 'first' or -Infinity: Adds to the "Head" zone. The first listener added as 'first' is placed at the very front.
|
|
67
73
|
* - 'last' or Infinity: Adds to the "Tail" zone. The first listener added as 'last' will always be the very last one to execute.
|
|
68
74
|
* - number: Inserts at the specified index within the "Body" (normal) zone.
|
|
@@ -88,8 +94,8 @@ export function getEventableMethods(aClass) {
|
|
|
88
94
|
data = data[RegExpEventSymbol] || (data[RegExpEventSymbol] = create(null))
|
|
89
95
|
}
|
|
90
96
|
|
|
91
|
-
const isFirst = index ===
|
|
92
|
-
const isLast = index ===
|
|
97
|
+
const isFirst = index === -Infinity || index === 'first'
|
|
98
|
+
const isLast = index === Infinity || index === 'last'
|
|
93
99
|
|
|
94
100
|
if (!data[type]) {
|
|
95
101
|
if (isFirst || isLast || typeof index === 'number') {
|
|
@@ -158,11 +164,14 @@ export function getEventableMethods(aClass) {
|
|
|
158
164
|
let fired = false
|
|
159
165
|
const self = this
|
|
160
166
|
|
|
161
|
-
|
|
167
|
+
function _once() {
|
|
162
168
|
self.off(type, _once)
|
|
163
169
|
if (!fired) {
|
|
164
170
|
fired = true
|
|
165
|
-
|
|
171
|
+
const result = listener.apply(this, arguments)
|
|
172
|
+
if (result instanceof Promise) {
|
|
173
|
+
return result
|
|
174
|
+
}
|
|
166
175
|
}
|
|
167
176
|
}
|
|
168
177
|
_once.listener = listener
|
|
@@ -183,6 +192,8 @@ export function getEventableMethods(aClass) {
|
|
|
183
192
|
const listeners = r.listeners
|
|
184
193
|
const evt = Event(this, r.type)
|
|
185
194
|
const errs = []
|
|
195
|
+
const opts = _getOptions(this)
|
|
196
|
+
let _throwErr
|
|
186
197
|
try {
|
|
187
198
|
let i = 0
|
|
188
199
|
let listener
|
|
@@ -191,6 +202,7 @@ export function getEventableMethods(aClass) {
|
|
|
191
202
|
_notify(listener, evt, args)
|
|
192
203
|
if (evt.stopped) {break}
|
|
193
204
|
} catch(err) {
|
|
205
|
+
if (opts.raiseError === true) {throw err}
|
|
194
206
|
errs.push({err: err, listener: listener})
|
|
195
207
|
}
|
|
196
208
|
++i
|
|
@@ -198,13 +210,18 @@ export function getEventableMethods(aClass) {
|
|
|
198
210
|
if (errs.length) {
|
|
199
211
|
for (let i=0;i<errs.length;i++) {
|
|
200
212
|
const it = errs[i]
|
|
213
|
+
if (r.type === 'error') {throw it.err}
|
|
201
214
|
this.emit('error', it.err, 'notify', r.type, it.listener, args)
|
|
202
215
|
}
|
|
203
216
|
}
|
|
204
217
|
} finally {
|
|
205
|
-
|
|
206
|
-
|
|
218
|
+
if (r.type === 'error' && opts.raiseError === null) {
|
|
219
|
+
const err = args[0]
|
|
220
|
+
_throwErr = err instanceof Error ? err : new Error(UnCAUGHT_ERR)
|
|
221
|
+
}
|
|
207
222
|
}
|
|
223
|
+
if (_throwErr) throw _throwErr
|
|
224
|
+
return evt.end()
|
|
208
225
|
},
|
|
209
226
|
|
|
210
227
|
/**
|
|
@@ -213,18 +230,35 @@ export function getEventableMethods(aClass) {
|
|
|
213
230
|
* @returns {Promise<*>} A promise that resolves with the result of the event.
|
|
214
231
|
*/
|
|
215
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
|
+
}
|
|
216
237
|
const r = _emit.apply(this, arguments)
|
|
217
238
|
if (!r) {return}
|
|
218
239
|
const args = r.args
|
|
219
240
|
const listeners = r.listeners
|
|
220
241
|
const evt = Event(this, r.type)
|
|
221
|
-
|
|
242
|
+
let _throwErr
|
|
222
243
|
try {
|
|
223
244
|
await _executeAsync.call(this, listeners, evt, args, options)
|
|
224
|
-
}
|
|
225
|
-
|
|
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
|
|
226
254
|
return evt.end()
|
|
227
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()
|
|
228
262
|
},
|
|
229
263
|
|
|
230
264
|
setMaxListeners(n) {
|
|
@@ -386,6 +420,16 @@ export function getEventableMethods(aClass) {
|
|
|
386
420
|
|
|
387
421
|
export default getEventableMethods
|
|
388
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
|
+
|
|
389
433
|
function _emit(type, msg) {
|
|
390
434
|
const data = this._events
|
|
391
435
|
let listeners
|
|
@@ -399,8 +443,24 @@ function _emit(type, msg) {
|
|
|
399
443
|
args.push(msg)
|
|
400
444
|
}
|
|
401
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
|
+
}
|
|
402
455
|
// If there is no 'error' event listener then throw.
|
|
403
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 */
|
|
404
464
|
if (!(msg instanceof Error)) {msg = new Error(msg ? UnCAUGHT_ERR + msg : UnCAUGHT_ERR)}
|
|
405
465
|
throw msg
|
|
406
466
|
}
|
|
@@ -450,9 +510,54 @@ function _notify(listener, evt, args) {
|
|
|
450
510
|
return result
|
|
451
511
|
}
|
|
452
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
|
+
|
|
453
557
|
async function _executeAsync(listeners, evt, args, options) {
|
|
454
558
|
const asyncMode = options.asyncMode || 'serial'
|
|
455
559
|
const resultMode = options.resultMode || 'last'
|
|
560
|
+
const signal = options.signal
|
|
456
561
|
const errs = []
|
|
457
562
|
|
|
458
563
|
if (resultMode === 'collect') {
|
|
@@ -472,6 +577,7 @@ async function _executeAsync(listeners, evt, args, options) {
|
|
|
472
577
|
}
|
|
473
578
|
return result
|
|
474
579
|
} catch (err) {
|
|
580
|
+
if (options.raiseError === true) {throw err}
|
|
475
581
|
errs.push({err, listener})
|
|
476
582
|
throw err
|
|
477
583
|
}
|
|
@@ -479,27 +585,54 @@ async function _executeAsync(listeners, evt, args, options) {
|
|
|
479
585
|
|
|
480
586
|
if (asyncMode === 'parallel') {
|
|
481
587
|
const promises = listeners.map(listener => notifyListener(listener))
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
await Promise.
|
|
487
|
-
|
|
488
|
-
|
|
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)
|
|
489
600
|
}
|
|
490
601
|
} else {
|
|
491
|
-
|
|
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
|
+
}
|
|
492
609
|
}
|
|
493
610
|
} else {
|
|
494
|
-
// Serial mode
|
|
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
|
|
495
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
|
+
|
|
496
622
|
try {
|
|
497
|
-
const result =
|
|
623
|
+
const result = canRaceAbort
|
|
624
|
+
? await Promise.race([notifyListener(listener), _createAbortPromise(signal, evt)])
|
|
625
|
+
: await notifyListener(listener)
|
|
498
626
|
if (resultMode === 'collect') {
|
|
499
627
|
evt.result.push(result)
|
|
500
628
|
}
|
|
501
629
|
if (evt.stopped || (resultMode === 'first' && evt.resolved)) break
|
|
502
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
|
|
503
636
|
if (resultMode === 'collect') {
|
|
504
637
|
evt.result.push(undefined)
|
|
505
638
|
}
|
|
@@ -507,10 +640,22 @@ async function _executeAsync(listeners, evt, args, options) {
|
|
|
507
640
|
}
|
|
508
641
|
}
|
|
509
642
|
|
|
510
|
-
|
|
643
|
+
if (errs.length) {
|
|
511
644
|
for (let i = 0; i < errs.length; i++) {
|
|
512
645
|
const it = errs[i]
|
|
513
|
-
|
|
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
|
+
}
|
|
514
655
|
}
|
|
515
656
|
}
|
|
657
|
+
|
|
658
|
+
if (evt.aborted) {
|
|
659
|
+
throw createAbortError()
|
|
660
|
+
}
|
|
516
661
|
}
|
package/src/event.js
CHANGED
|
@@ -30,6 +30,12 @@ Event.prototype.init = function(target, type) {
|
|
|
30
30
|
* @public
|
|
31
31
|
*/
|
|
32
32
|
this.stopped = false
|
|
33
|
+
/**
|
|
34
|
+
* Whether the event emission was aborted via AbortSignal.
|
|
35
|
+
* @type {boolean}
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
this.aborted = false
|
|
33
39
|
/**
|
|
34
40
|
* Whether a result has been resolved (for 'first' result mode)
|
|
35
41
|
* @type {boolean}
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// @sourceType: module
|
|
2
|
+
import validObject from './util/valid-object'
|
|
3
|
+
import {createAbortError} from './consts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns a Promise that resolves with the Event object when the specified event is emitted on the given emitter.
|
|
7
|
+
* If an 'error' event is emitted (and the waiting event is not 'error'), the promise rejects by default.
|
|
8
|
+
* If the provided AbortSignal is aborted, the promise rejects with an AbortError.
|
|
9
|
+
*
|
|
10
|
+
* Note: The resolved Event object's `result` field may not be the final value if other listeners have not yet run.
|
|
11
|
+
* For the definitive emit return value, use `emit()` or `emitAsync()` directly.
|
|
12
|
+
*
|
|
13
|
+
* @param {import('./event-emitter').EventEmitter} emitter - The event emitter to listen on.
|
|
14
|
+
* @param {string|RegExp} type - The event type to wait for.
|
|
15
|
+
* @param {Object} [options] - Optional configuration.
|
|
16
|
+
* @param {AbortSignal} [options.signal] - An AbortSignal to cancel the wait.
|
|
17
|
+
* @param {boolean|null} [options.raiseError] - Controls behavior when an 'error' event is emitted:
|
|
18
|
+
* - `true` / `undefined` (default): The promise rejects with the error.
|
|
19
|
+
* - `false`: The promise resolves with the error object instead of rejecting.
|
|
20
|
+
* @returns {Promise<import('./event').Event>} - A promise that resolves with the Event object.
|
|
21
|
+
* @throws {TypeError} - If emitter is not a valid event emitter object.
|
|
22
|
+
*/
|
|
23
|
+
export function oncePromise(emitter, type, options) {
|
|
24
|
+
validObject(emitter)
|
|
25
|
+
|
|
26
|
+
const signal = options && options.signal
|
|
27
|
+
const raiseError = options && options.raiseError
|
|
28
|
+
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
let onAbort = null
|
|
31
|
+
let aborted = false
|
|
32
|
+
|
|
33
|
+
const cleanup = () => {
|
|
34
|
+
emitter.off(type, onEvent)
|
|
35
|
+
if (type !== 'error') {
|
|
36
|
+
emitter.off('error', onError)
|
|
37
|
+
}
|
|
38
|
+
if (signal && onAbort) {
|
|
39
|
+
signal.removeEventListener('abort', onAbort)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (signal) {
|
|
44
|
+
if (signal.aborted) {
|
|
45
|
+
reject(createAbortError())
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
onAbort = () => {
|
|
49
|
+
/* v8 ignore next — defensive guard, { once: true } ensures callback fires at most once */
|
|
50
|
+
if (!aborted) {
|
|
51
|
+
aborted = true
|
|
52
|
+
cleanup()
|
|
53
|
+
reject(createAbortError())
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
signal.addEventListener('abort', onAbort, { once: true })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function onEvent() {
|
|
60
|
+
cleanup()
|
|
61
|
+
resolve(this) // resolve with the Event object
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function onError(err) {
|
|
65
|
+
cleanup()
|
|
66
|
+
if (raiseError === false) {
|
|
67
|
+
resolve(err) // resolve with the error instead of rejecting
|
|
68
|
+
} else {
|
|
69
|
+
reject(err) // true | null | undefined → reject
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
emitter.on(type, onEvent)
|
|
74
|
+
if (type !== 'error') {
|
|
75
|
+
emitter.on('error', onError)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default oncePromise
|
package/src/pipe-async.js
CHANGED
|
@@ -17,7 +17,9 @@ const emit = methods.emitAsync
|
|
|
17
17
|
* @param {string} [name='emitAsync'] - The name of the event to pipe (defaults to 'emitAsync').
|
|
18
18
|
* @param {Object} [options] - Configuration for the pipeline.
|
|
19
19
|
* @param {string} [options.asyncMode='serial'] - The mode of propagation ('serial' or 'parallel').
|
|
20
|
-
* @param {string} [options.resultMode] - Strategy for aggregating results from the pipe chain ('
|
|
20
|
+
* @param {string} [options.resultMode='last'] - Strategy for aggregating results from the pipe chain ('last', 'first', 'collect').
|
|
21
|
+
* @param {AbortSignal} [options.signal] - An AbortSignal to cancel forwarding to remaining pipe targets (serial mode only).
|
|
22
|
+
* Can also be set on the source emitter via `configure({ signal })`.
|
|
21
23
|
* @returns {Object} - An object with a `close` method that removes the pipeline between the two event emitters.
|
|
22
24
|
* @throws {TypeError} - If either of the arguments is not an event emitter object.
|
|
23
25
|
*/
|
|
@@ -59,6 +61,9 @@ export function pipeAsync(e1, e2/* , name, options */) {
|
|
|
59
61
|
return fn.apply(target, args)
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
// Read signal from source emitter's runtime options, if configured
|
|
65
|
+
const signal = this._eeRuntimeOptions && this._eeRuntimeOptions.signal
|
|
66
|
+
|
|
62
67
|
if (asyncMode === 'parallel') {
|
|
63
68
|
const promises = [emit.apply(this, arguments)]
|
|
64
69
|
for (let i = 0; i < data.length; ++i) {
|
|
@@ -67,17 +72,25 @@ export function pipeAsync(e1, e2/* , name, options */) {
|
|
|
67
72
|
const results = await Promise.all(promises)
|
|
68
73
|
if (resultMode === 'collect') return results
|
|
69
74
|
if (resultMode === 'first') return results.find(r => r !== undefined)
|
|
70
|
-
|
|
75
|
+
// Default: 'last' — return last non-undefined result in the pipe chain
|
|
76
|
+
for (let i = results.length - 1; i >= 0; i--) {
|
|
77
|
+
if (results[i] !== undefined) return results[i]
|
|
78
|
+
}
|
|
71
79
|
} else {
|
|
72
80
|
const mainResult = await emit.apply(this, arguments)
|
|
73
81
|
const allResults = [mainResult]
|
|
74
82
|
for (let i = 0; i < data.length; ++i) {
|
|
83
|
+
// Check signal before forwarding to each pipe target in serial mode
|
|
84
|
+
if (signal && signal.aborted) break
|
|
75
85
|
const res = await forward(data[i], arguments)
|
|
76
86
|
allResults.push(res)
|
|
77
87
|
}
|
|
78
88
|
if (resultMode === 'collect') return allResults
|
|
79
89
|
if (resultMode === 'first') return allResults.find(r => r !== undefined)
|
|
80
|
-
|
|
90
|
+
// Default: 'last' — return last non-undefined result in the pipe chain
|
|
91
|
+
for (let i = allResults.length - 1; i >= 0; i--) {
|
|
92
|
+
if (allResults[i] !== undefined) return allResults[i]
|
|
93
|
+
}
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
defineProperty(e1, name, desc.value, desc)
|
package/src/unify.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import validObject from './util/valid-object'
|
|
2
2
|
import forEach from './util/object-for-each'
|
|
3
3
|
|
|
4
|
-
const push = Array.prototype.
|
|
4
|
+
const push = Array.prototype.push
|
|
5
5
|
const defineProperty = Object.defineProperty
|
|
6
6
|
const create = Object.create
|
|
7
7
|
const hasOwnProperty = Object.prototype.hasOwnProperty
|
package/tsconfig.json
CHANGED
package/vitest.config.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
|
|
8
|
+
// util-ex has an exports field that only exposes "." and blocks deep
|
|
9
|
+
// imports like 'util-ex/lib/defineProperty'. Dependencies like
|
|
10
|
+
// custom-ability also import many util-ex/lib/ subpaths via bare
|
|
11
|
+
// specifiers (e.g., 'util-ex/lib/is/type/array').
|
|
12
|
+
//
|
|
13
|
+
// Vite's resolver strictly enforces the exports field, so we need
|
|
14
|
+
// to intercept resolution at a low level. We do this by having a
|
|
15
|
+
// Vite plugin that patches the util-ex package.json at config
|
|
16
|
+
// resolution time, adding wildcard exports for the lib directory.
|
|
17
|
+
|
|
18
|
+
export default defineConfig({
|
|
19
|
+
plugins: [
|
|
20
|
+
{
|
|
21
|
+
name: 'fix-util-ex-resolution',
|
|
22
|
+
configResolved() {
|
|
23
|
+
const pkgPath = path.resolve(__dirname, 'node_modules/util-ex/package.json')
|
|
24
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
|
25
|
+
if (!pkg._patched) {
|
|
26
|
+
pkg.exports = {
|
|
27
|
+
'.': pkg.exports['.'],
|
|
28
|
+
'./lib/*': {
|
|
29
|
+
import: './lib/*.mjs',
|
|
30
|
+
require: './lib/*.js',
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
pkg._patched = true
|
|
34
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
test: {
|
|
40
|
+
globals: true,
|
|
41
|
+
include: ['test/**/*-test.js'],
|
|
42
|
+
reporters: ['verbose'],
|
|
43
|
+
coverage: {
|
|
44
|
+
provider: 'v8',
|
|
45
|
+
reportsDirectory: './coverage',
|
|
46
|
+
reporter: ['text', 'html', 'lcov', 'clover'],
|
|
47
|
+
include: ['src/**'],
|
|
48
|
+
exclude: [
|
|
49
|
+
'src/index.js',
|
|
50
|
+
'src/event-emitter.d.ts',
|
|
51
|
+
'src/util/**',
|
|
52
|
+
],
|
|
53
|
+
thresholds: {
|
|
54
|
+
statements: 80,
|
|
55
|
+
branches: 70,
|
|
56
|
+
functions: 80,
|
|
57
|
+
lines: 80,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
})
|
package/.mocharc.yml
DELETED