events-ex 2.3.1 → 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 +18 -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 +33 -17
  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 +171 -19
  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 +166 -21
  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
package/.babelrc CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "plugins": [
3
- ["@babel/plugin-proposal-dynamic-import"],
3
+ ["@babel/plugin-transform-dynamic-import"],
4
4
  ["@babel/plugin-transform-modules-commonjs", {
5
5
  "allowTopLevelThis": true,
6
6
  // "importInterop": "node",
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [2.4.0](https://github.com/snowyu/events-ex.js/compare/v2.3.1...v2.4.0) (2026-07-07)
6
+
7
+
8
+ ### Features
9
+
10
+ * add AbortSignal support for async event emission ([ca3e851](https://github.com/snowyu/events-ex.js/commit/ca3e8519ec6c87de8e0ab0ef824b63b2abb1d391))
11
+ * add oncePromise to convert event into Promise ([fe80487](https://github.com/snowyu/events-ex.js/commit/fe8048727b78365569cedc7d4aa9b71bfb01d715))
12
+ * add raiseError option for error handling control ([d26b490](https://github.com/snowyu/events-ex.js/commit/d26b4907a6217095b9de61e0f698145fe9e2b7c6))
13
+ * update esm support ([d26bdae](https://github.com/snowyu/events-ex.js/commit/d26bdae527dff2296ff52d0d7b94d37c9f694683))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * **build:** strictly mjs import and chagnes ([1738d9b](https://github.com/snowyu/events-ex.js/commit/1738d9bce8d1eae4826213336afe3c0c8e5fa482))
19
+ * emitAsync error-in-error propagation and error re-emission ([94f2e0f](https://github.com/snowyu/events-ex.js/commit/94f2e0f8f24029fcbc1455eee2a0f0fe299f10de))
20
+ * **pipe-async:** default resultMode to 'last' to align with emitAsync ([a54de61](https://github.com/snowyu/events-ex.js/commit/a54de6103fb895e9096c764ea481fcc03e62f7aa))
21
+ * prevent infinite recursion when error listener throws ([a52cebb](https://github.com/snowyu/events-ex.js/commit/a52cebb0771acddb2940a8e4d8868b4e0e5d7b04))
22
+
5
23
  ## [2.3.1](https://github.com/snowyu/events-ex.js/compare/v2.3.0...v2.3.1) (2026-04-17)
6
24
 
7
25
 
package/README.cn.md CHANGED
@@ -14,8 +14,9 @@
14
14
  * **可配置的并发性**:异步监听器支持 **顺序(Serial)**(默认)和 **并发(Parallel)** 执行。
15
15
  * **结果聚合策略**:支持多种收集返回值的方式:`last`(默认)、`first`(首个成功结果)和 `collect`(所有结果)。
16
16
  * **Fluent API 代理**:通过 `.parallel()` 和 `.configure()` 提供无副作用的临时执行上下文。
17
+ * **AbortSignal 支持**:通过 `configure({ signal })` 支持取消异步事件发射,也可用于 `oncePromise`。
17
18
  * **架构优势**:重写核心以提升性能与灵活性,同时保持广泛的兼容性。
18
- * **事件工具集**:内置支持 `pipe`, `pipeAsync`, `unify`, `allOff` 和 `hasListeners`。
19
+ * **事件工具集**:内置支持 `pipe`, `pipeAsync`, `oncePromise`, `unify`, `allOff` 和 `hasListeners`。
19
20
 
20
21
  ### 区别
21
22
 
@@ -24,6 +25,7 @@
24
25
  * 事件对象(`Event Object`)作为监听器的 "this" 对象。
25
26
  * `result` 属性: 可选, 如果设置,则将该结果返回到事件发射器(`Event Emitter`)。
26
27
  * `stopped` 属性: 可选, 如果设置为 `true`,则会阻止剩余的监听器被执行。
28
+ * `aborted` 属性: (仅异步) `true` 表示事件发射被 `AbortSignal` 取消。
27
29
  * `target`属性: 事件发射器对象,原本的`this`
28
30
  * `type`属性: 触发的事件类型名称
29
31
  * `resolved`: (仅异步) 在 `first` 模式下,标识是否已找到成功的结果。
@@ -39,7 +41,7 @@
39
41
  * `collect`: 按注册顺序以数组形式返回所有结果。
40
42
  * **流式配置**: 使用 `.parallel()` 或 `.configure({...})` 进行单次定制化异步发射。
41
43
  * **事件监听器 API**: `on/once(event: string|RegExp, listener, index?: number|'first'|'last')`
42
- * 📌 **Index 参数** (可选): 在监听器数组中指定插入位置。
44
+ * 📌 **Index 参数** (可选): 在监听器数组中指定插入位置。
43
45
  * `'first'` (`-Infinity`): 始终保持在 **Head** 区。先注册的 `'first'` 监听器排在最前面。
44
46
  * `'last'` (`Infinity`): 始终保持在 **Tail** 区。先注册为 `'last'` 的监听器将始终位于数组的绝对末尾。
45
47
  * `number`: 常规 **Body** 区内的相对索引。
@@ -54,6 +56,13 @@
54
56
  * 添加了 `listenerCount()` 类方法,以保持与 Node 事件模块的兼容性。
55
57
  * 添加了`emitAsync`等方法,支持异步事件
56
58
 
59
+ * 🔗 **事件管道与统一**:
60
+ * `pipe(source, target)`: 同步事件转发。
61
+ * `pipeAsync(source, target, options)`: 异步转发,支持可配置的并发性与聚合。
62
+ * `unify(emitter1, emitter2)`: 双向事件同步。
63
+
64
+ > 注意:监听器抛出的错误不会打断通知流程——捕获到的错误会在所有监听器执行完毕后,以 `'error'` 事件形式重新触发。**例外**:如果错误是在 `'error'` 事件监听器内部抛出的,它会直接作为异常(同步)或 Promise rejection(异步)向外传播而非再次触发,以**避免无限递归/栈溢出**。此机制同时适用于 `emit()` 和 `emitAsync()`。
65
+
57
66
  ---
58
67
 
59
68
  ### 用法
@@ -140,6 +149,44 @@ const allResults = await ee.parallel('collect').emitAsync('task');
140
149
  const firstResult = await ee.parallel('first').emitAsync('task');
141
150
  ```
142
151
 
152
+ #### AbortSignal 支持(仅针对异步)
153
+
154
+ 通过 `configure({ signal })` 或 `oncePromise` 的 `options` 参数支持标准的 `AbortSignal`,可取消异步事件发射。
155
+
156
+ ```js
157
+ const emitter = new EventEmitter();
158
+ const controller = new AbortController();
159
+
160
+ emitter.on('task', async () => {
161
+ await sleep(500);
162
+ return '完成';
163
+ });
164
+
165
+ // emitAsync: 通过 configure 传递 signal
166
+ setTimeout(() => controller.abort(), 200); // 200ms 后取消
167
+ try {
168
+ await emitter.configure({ signal: controller.signal }).emitAsync('task');
169
+ } catch (err) {
170
+ console.log(err.name); // 'AbortError'
171
+ }
172
+
173
+ // oncePromise: 通过 options 直接传递 signal
174
+ const c2 = new AbortController();
175
+ setTimeout(() => c2.abort(), 100);
176
+ try {
177
+ await oncePromise(emitter, 'ready', { signal: c2.signal });
178
+ } catch (err) {
179
+ console.log(err.name); // 'AbortError'
180
+ }
181
+ ```
182
+
183
+ **行为说明**:
184
+
185
+ - **串行模式 (Serial)**:每个 listener 执行前检查 `signal.aborted`,触发则立即中断并抛出 `AbortError`。当显式设置了 `raiseError`(`true`/`false`/`null`)时,还会通过 `Promise.race` 打断**正在执行**的 listener。
186
+ - **并发模式 (Parallel)**:用 `Promise.race` 将 listener 执行与 signal 竞速,signal 触发立即抛出 `AbortError`。
187
+ - **pipeAsync**:串行模式下,向每个 pipe target 转发前检查 source 的 signal,已 abort 则跳过后续 targets。
188
+ - `Event` 对象新增 `aborted` 字段,独立于 `stopped`,用于追踪取消状态。
189
+
143
190
  ### 高级特性
144
191
 
145
192
  #### 异步并发引擎 (仅针对 `emitAsync`)
@@ -151,6 +198,11 @@ const firstResult = await ee.parallel('first').emitAsync('task');
151
198
  | **`resultMode`** | `'last'` | **(默认)** 返回最后一个监听器的结果(并发模式下为最后一个完成的)。 |
152
199
  | | `'first'` | 返回第一个 **非 undefined** 且 **成功** 的结果。自动跳过错误。 |
153
200
  | | `'collect'` | 按注册顺序以数组形式返回所有监听器的结果。 |
201
+ | **`signal`** | `AbortSignal` | 通过 `AbortController` 创建的信号,用于取消异步事件发射。仅在 `configure()` 中传递,不会固化到实例上。 |
202
+ | **`raiseError`** | `true` | 立即抛出所有监听器错误。并发模式下多个错误会聚合为 `AggregateError`。 |
203
+ | | `false` | 静默吞掉监听器错误。与 `signal` 配合时可在串行模式下打断正在执行的监听器(启用 `Promise.race`)。 |
204
+ | | `null` | **(同步 `emit` 默认值)** 仅针对 `error` 事件:无 error 监听器时抛出(Node.js 默认行为)。 |
205
+ | | `undefined` | **(默认值)** 对 `emitAsync` 等同于 `false`。保持现有行为不变。 |
154
206
 
155
207
  #### 代理隔离 (Fluent API)
156
208
 
@@ -204,9 +256,68 @@ eventable(MyClass, {
204
256
 
205
257
  创建异步管道。支持配置传播模式和结果聚合策略。
206
258
 
259
+ - `options.asyncMode`: 传播模式 (`'serial' | 'parallel'`)。
260
+ - `options.resultMode`: 结果聚合策略(`'last'` 默认 | `'first'` | `'collect'`)。
261
+
262
+ #### oncePromise(emitter, type[, options]) _(events-ex/once-promise)_
263
+
264
+ 返回一个 `Promise`,当指定事件在 emitter 上触发时,resolve 并传入 **Event 对象**。
265
+ 如果 emitter 触发了 `error` 事件(且等待的事件不是 `error`),则 Promise reject。
266
+ 如果提供的 `AbortSignal` 被取消,则 Promise reject 并抛出 `AbortError`。
267
+
268
+ - `emitter` _(EventEmitter)_: 要监听的事件发射器。
269
+ - `type` _(string | RegExp)_: 要等待的事件类型。支持正则表达式匹配多个事件。
270
+ - `options` _(Object)_: 可选配置。
271
+ - `signal` _(AbortSignal)_: 用于取消等待的 AbortSignal。
272
+ - `raiseError` _(boolean|null)_: 控制当 emitter 触发 `'error'` 事件时的行为。
273
+ - `true` / `undefined` **(默认)**: Promise reject 并传入错误对象。
274
+ - `false`: Promise resolve 并传入错误对象,而非 reject。
275
+ - 返回: `Promise<Event>` — resolve 时传入 Event 对象,包含 `type`、`target` 等属性。
276
+
277
+ > 注意:返回的 Event 对象中的 `result` 字段可能不是最终值(如果还有其他监听器尚未执行)。如需获取 emit 的最终返回值,请直接使用 `emit()` 或 `emitAsync()`。
278
+
279
+ ```js
280
+ import {oncePromise, EventEmitter} from 'events-ex';
281
+
282
+ const ee = new EventEmitter();
283
+
284
+ // 等待 data 事件
285
+ setTimeout(() => ee.emit('data', { id: 1 }), 100);
286
+ const evt = await oncePromise(ee, 'data');
287
+ console.log(evt.type); // 'data'
288
+ console.log(evt.target); // 发射器对象
289
+
290
+ // 等待正则匹配的事件 – evt.type 会告诉你实际触发的事件类型
291
+ setTimeout(() => ee.emit('user.login', { name: 'Alice' }), 100);
292
+ const evt2 = await oncePromise(ee, /^user\./);
293
+ console.log(evt2.type); // 'user.login' (而非正则表达式)
294
+
295
+ // 错误处理:触发 error 时会 reject(除非等待的就是 'error')
296
+ try {
297
+ await oncePromise(ee, 'data');
298
+ } catch (err) {
299
+ console.error('发生错误:', err);
300
+ }
301
+
302
+ // 等待 'error' 事件会正常 resolve
303
+ ee.emit('error', new Error('预期的错误'));
304
+ await oncePromise(ee, 'error'); // 正常 resolve,不会 reject
305
+
306
+ // 配合 AbortSignal 实现超时取消
307
+ const controller = new AbortController();
308
+ setTimeout(() => controller.abort(), 5000);
309
+ try {
310
+ const evt = await oncePromise(ee, 'response', { signal: controller.signal });
311
+ } catch (err) {
312
+ if (err.name === 'AbortError') {
313
+ console.log('等待超时或被取消');
314
+ }
315
+ }
316
+ ```
317
+
207
318
  #### setEmitterOptions(options)
208
319
 
209
- 配置实例级的默认选项。
320
+ 配置实例级的默认选项,包括 `asyncMode`、`resultMode`、`maxListeners` 和 `raiseError`。
210
321
 
211
322
  [event-emitter]: https://github.com/medikoo/event-emitter
212
323
  [Ability]: https://github.com/snowyu/custom-ability.js
package/README.md CHANGED
@@ -14,8 +14,9 @@ Browser-friendly enhanced event emitter [ability][Ability] and class. It's modif
14
14
  * **Configurable Concurrency**: Choose between **Serial** (default) and **Parallel** execution for async listeners.
15
15
  * **Result Aggregation**: Strategies to gather return values: `last` (default), `first` (first success), and `collect` (all results).
16
16
  * **Fluent API Proxies**: Use `.parallel()` and `.configure()` for transient, side-effect-free execution context.
17
+ * **AbortSignal Support**: Cancel async event emissions via `configure({ signal })` or `oncePromise` with an `AbortSignal`.
17
18
  * **Architecture**: Rewritten core for improved performance and flexibility while maintaining broad compatibility.
18
- * **Event Utilities**: Built-in support for `pipe`, `pipeAsync`, `unify`, `allOff`, and `hasListeners`.
19
+ * **Event Utilities**: Built-in support for `pipe`, `pipeAsync`, `oncePromise`, `unify`, `allOff`, and `hasListeners`.
19
20
 
20
21
  ### Differences
21
22
 
@@ -24,6 +25,7 @@ Browser-friendly enhanced event emitter [ability][Ability] and class. It's modif
24
25
  + the `event object` as listener's "this" object:
25
26
  * `result`: If set, the result is returned to the `Event Emitter`.
26
27
  * `stopped`: If set to `true`, it prevents the remaining listeners from being executed.
28
+ * `aborted`: (Async only) `true` if the emission was cancelled by an `AbortSignal`.
27
29
  * `target`: The `Event Emitter` object, which was originally the `this` object.
28
30
  * `type`: triggered event type(name).
29
31
  * `resolved`: (Async only) Indicates if a successful result has been found in `first` mode.
@@ -39,7 +41,7 @@ Browser-friendly enhanced event emitter [ability][Ability] and class. It's modif
39
41
  * `collect`: Returns an array of all results in registration order.
40
42
  * **Fluent Configuration**: Use `.parallel()` or `.configure({...})` for one-time customized async emits.
41
43
  * **Listener APIs**: `on/once(event: string|RegExp, listener, index?: number|'first'|'last')`
42
- * 📌 **Index Parameter** (Optional): Insertion position in the listener array.
44
+ * 📌 **Index Parameter** (Optional): Insertion position in the listener array.
43
45
  * `'first'` (`-Infinity`): Stays in the **Head** zone. The first listener added as `'first'` is placed at the very front.
44
46
  * `'last'` (`Infinity`): Stays in the **Tail** zone. The first listener added as `'last'` will always remain at the absolute end.
45
47
  * `number`: Relative index within the **Body** zone.
@@ -59,7 +61,7 @@ Browser-friendly enhanced event emitter [ability][Ability] and class. It's modif
59
61
  * `pipeAsync(source, target, options)`: Async forwarding with configurable concurrency and aggregation.
60
62
  * `unify(emitter1, emitter2)`: Bi-directional synchronization.
61
63
 
62
- Note: The listener throw error should not broke the notification, but it will emit error(`emit('error', error, 'notify', eventName, listener, args)`) after notification.
64
+ Note: A listener's throw error does not break the notification flow—caught errors are collected and then re-emitted as an `'error'` event after all listeners run. **Exception**: If the error is thrown inside an `'error'` event listener itself, it propagates directly as a thrown exception (sync) or a rejected promise (async), preventing infinite recursion / stack overflow. This applies to both `emit()` and `emitAsync()`.
63
65
 
64
66
  ### Installation
65
67
 
@@ -170,6 +172,44 @@ const allResults = await ee.parallel('collect').emitAsync('task');
170
172
  const firstResult = await ee.parallel('first').emitAsync('task');
171
173
  ```
172
174
 
175
+ #### AbortSignal Support (Async Only)
176
+
177
+ Cancel async event emissions using the standard `AbortSignal` via `configure({ signal })` or `oncePromise` options.
178
+
179
+ ```js
180
+ const emitter = new EventEmitter();
181
+ const controller = new AbortController();
182
+
183
+ emitter.on('task', async () => {
184
+ await sleep(500);
185
+ return 'done';
186
+ });
187
+
188
+ // emitAsync: pass signal via configure
189
+ setTimeout(() => controller.abort(), 200); // cancel after 200ms
190
+ try {
191
+ await emitter.configure({ signal: controller.signal }).emitAsync('task');
192
+ } catch (err) {
193
+ console.log(err.name); // 'AbortError'
194
+ }
195
+
196
+ // oncePromise: pass signal directly via options
197
+ const c2 = new AbortController();
198
+ setTimeout(() => c2.abort(), 100);
199
+ try {
200
+ await oncePromise(emitter, 'ready', { signal: c2.signal });
201
+ } catch (err) {
202
+ console.log(err.name); // 'AbortError'
203
+ }
204
+ ```
205
+
206
+ **Behavior**:
207
+
208
+ - **Serial mode**: Checks `signal.aborted` before each listener, throws `AbortError` immediately when triggered. When `raiseError` is explicitly set (`true`/`false`/`null`), uses `Promise.race` to interrupt an actively running listener as well.
209
+ - **Parallel mode**: Races listener execution against the signal via `Promise.race`. Throws `AbortError` when the signal wins.
210
+ - **pipeAsync**: In serial mode, checks the source's signal before forwarding to each pipe target; skips remaining targets if aborted.
211
+ - `Event` objects have an `aborted` field (independent of `stopped`) to track cancellation state.
212
+
173
213
  ### Advanced Features
174
214
 
175
215
  #### Async Concurrency Engine (For `emitAsync` Only)
@@ -181,6 +221,11 @@ const firstResult = await ee.parallel('first').emitAsync('task');
181
221
  | **`resultMode`** | `'last'` | **(Default)** Returns the result of the last listener (or last to finish). |
182
222
  | | `'first'` | Returns the first **non-undefined** and **successful** result. Skips errors. |
183
223
  | | `'collect'` | Returns an array of all results in registration order. |
224
+ | **`signal`** | `AbortSignal` | An `AbortSignal` from an `AbortController` to cancel async event emission. Only passed via `configure()`, not stored on the instance. |
225
+ | **`raiseError`** | `true` | Throws all listener errors immediately. Parallel mode aggregates multiple errors into an `AggregateError`. |
226
+ | | `false` | Silently swallows listener errors. Used with `signal` to interrupt an executing listener in serial mode (added `Promise.race`). |
227
+ | | `null` | **(Default for sync `emit`)** For `'error'` events only: throws if there are no error listeners (Node.js default behavior). |
228
+ | | `undefined` | **(Default)** Same as `false` for `emitAsync`. Keeps existing behavior unchanged. |
184
229
 
185
230
  #### Proxy Isolation (Fluent API)
186
231
 
@@ -248,11 +293,67 @@ hasListeners(emitter, 'foo'); // true
248
293
  Creates an asynchronous pipeline.
249
294
 
250
295
  - `options.asyncMode`: Propagation mode (`'serial' | 'parallel'`).
251
- - `options.resultMode`: Aggregation strategy.
296
+ - `options.resultMode`: Aggregation strategy (`'last'` default | `'first'` | `'collect'`).
297
+
298
+ #### oncePromise(emitter, type[, options]) _(events-ex/once-promise)_
299
+
300
+ Returns a `Promise` that resolves with the **Event object** when the specified event is emitted on the given emitter.
301
+ If an `error` event is emitted (and the waiting event is not `error`), the promise rejects.
302
+ If the provided `AbortSignal` is aborted, the promise rejects with an `AbortError`.
303
+
304
+ - `emitter` _(EventEmitter)_: The event emitter to listen on.
305
+ - `type` _(string | RegExp)_: The event type to wait for. Supports regex for matching multiple events.
306
+ - `options` _(Object)_: Optional configuration.
307
+ - `signal` _(AbortSignal)_: An AbortSignal to cancel the wait.
308
+ - `raiseError` _(boolean|null)_: Controls behavior when an `'error'` event is emitted on the emitter.
309
+ - `true` / `undefined` **(default)**: The promise rejects with the error.
310
+ - `false`: The promise resolves with the error object instead of rejecting.
311
+ - Returns: `Promise<Event>` — resolves with the Event object, which provides `type`, `target`, etc.
312
+
313
+ > Note: The resolved Event object's `result` field may not be the final value if other listeners have not yet run. For the definitive emit return value, use `emit()` or `emitAsync()` directly.
314
+
315
+ ```js
316
+ import {oncePromise, EventEmitter} from 'events-ex';
317
+
318
+ const ee = new EventEmitter();
319
+
320
+ // Wait for a data event
321
+ setTimeout(() => ee.emit('data', { id: 1 }), 100);
322
+ const evt = await oncePromise(ee, 'data');
323
+ console.log(evt.type); // 'data'
324
+ console.log(evt.target); // the emitter
325
+
326
+ // Wait for a regex-matched event – evt.type reveals the actual event
327
+ setTimeout(() => ee.emit('user.login', { name: 'Alice' }), 100);
328
+ const evt2 = await oncePromise(ee, /^user\./);
329
+ console.log(evt2.type); // 'user.login' (not the regex)
330
+
331
+ // Error handling: rejects on error (unless waiting for 'error')
332
+ try {
333
+ await oncePromise(ee, 'data');
334
+ } catch (err) {
335
+ console.error('Error occurred:', err);
336
+ }
337
+
338
+ // Waiting for 'error' event resolves normally
339
+ ee.emit('error', new Error('expected'));
340
+ await oncePromise(ee, 'error'); // resolves, not rejects
341
+
342
+ // Use AbortSignal for timeout cancellation
343
+ const controller = new AbortController();
344
+ setTimeout(() => controller.abort(), 5000);
345
+ try {
346
+ const evt = await oncePromise(ee, 'response', { signal: controller.signal });
347
+ } catch (err) {
348
+ if (err.name === 'AbortError') {
349
+ console.log('timed out or cancelled');
350
+ }
351
+ }
352
+ ```
252
353
 
253
354
  #### setEmitterOptions(options)
254
355
 
255
- Configures instance-wide defaults for `asyncMode`, `resultMode`, and `maxListeners`.
356
+ Configures instance-wide defaults for `asyncMode`, `resultMode`, `maxListeners`, and `raiseError`.
256
357
 
257
358
  [event-emitter]: https://github.com/medikoo/event-emitter
258
359
  [Ability]: https://github.com/snowyu/custom-ability.js
@@ -0,0 +1,133 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for all-off.js</title>
7
+ <meta charset="utf-8" />
8
+ <link rel="stylesheet" href="prettify.css" />
9
+ <link rel="stylesheet" href="base.css" />
10
+ <link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <style type='text/css'>
13
+ .coverage-summary .sorter {
14
+ background-image: url(sort-arrow-sprite.png);
15
+ }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <div class='wrapper'>
21
+ <div class='pad1'>
22
+ <h1><a href="index.html">All files</a> all-off.js</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">100% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>2/2</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">100% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>0/0</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">100% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>1/1</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">100% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>2/2</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line high'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a>
69
+ <a name='L4'></a><a href='#L4'>4</a>
70
+ <a name='L5'></a><a href='#L5'>5</a>
71
+ <a name='L6'></a><a href='#L6'>6</a>
72
+ <a name='L7'></a><a href='#L7'>7</a>
73
+ <a name='L8'></a><a href='#L8'>8</a>
74
+ <a name='L9'></a><a href='#L9'>9</a>
75
+ <a name='L10'></a><a href='#L10'>10</a>
76
+ <a name='L11'></a><a href='#L11'>11</a>
77
+ <a name='L12'></a><a href='#L12'>12</a>
78
+ <a name='L13'></a><a href='#L13'>13</a>
79
+ <a name='L14'></a><a href='#L14'>14</a>
80
+ <a name='L15'></a><a href='#L15'>15</a>
81
+ <a name='L16'></a><a href='#L16'>16</a>
82
+ <a name='L17'></a><a href='#L17'>17</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
83
+ <span class="cline-any cline-neutral">&nbsp;</span>
84
+ <span class="cline-any cline-yes">21x</span>
85
+ <span class="cline-any cline-neutral">&nbsp;</span>
86
+ <span class="cline-any cline-neutral">&nbsp;</span>
87
+ <span class="cline-any cline-neutral">&nbsp;</span>
88
+ <span class="cline-any cline-neutral">&nbsp;</span>
89
+ <span class="cline-any cline-neutral">&nbsp;</span>
90
+ <span class="cline-any cline-neutral">&nbsp;</span>
91
+ <span class="cline-any cline-neutral">&nbsp;</span>
92
+ <span class="cline-any cline-neutral">&nbsp;</span>
93
+ <span class="cline-any cline-neutral">&nbsp;</span>
94
+ <span class="cline-any cline-yes">2x</span>
95
+ <span class="cline-any cline-neutral">&nbsp;</span>
96
+ <span class="cline-any cline-neutral">&nbsp;</span>
97
+ <span class="cline-any cline-neutral">&nbsp;</span>
98
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import eventable from './eventable'
99
+ &nbsp;
100
+ const removeAllListeners = eventable().methods.removeAllListeners;
101
+ &nbsp;
102
+ /**
103
+ * Removes all listeners for a specific event or all events from an event emitter.
104
+ *
105
+ * @param {import('./event-emitter').EventEmitter} emitter - The event emitter to remove listeners from.
106
+ * @param {string} [type] - The event to remove listeners for. If not provided, all listeners for all events will be removed.
107
+ * @returns {import('./event-emitter').EventEmitter} - The event emitter with all listeners removed.
108
+ */
109
+ export function allOff(emitter, type) {
110
+ return removeAllListeners.call(emitter, type);
111
+ };
112
+ &nbsp;
113
+ export default allOff;
114
+ &nbsp;</pre></td></tr></table></pre>
115
+
116
+ <div class='push'></div><!-- for sticky footer -->
117
+ </div><!-- /wrapper -->
118
+ <div class='footer quiet pad2 space-top1 center small'>
119
+ Code coverage generated by
120
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
121
+ at 2026-07-07T07:31:06.609Z
122
+ </div>
123
+ <script src="prettify.js"></script>
124
+ <script>
125
+ window.onload = function () {
126
+ prettyPrint();
127
+ };
128
+ </script>
129
+ <script src="sorter.js"></script>
130
+ <script src="block-navigation.js"></script>
131
+ </body>
132
+ </html>
133
+