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/.babelrc
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
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
|
+
|
|
23
|
+
## [2.3.1](https://github.com/snowyu/events-ex.js/compare/v2.3.0...v2.3.1) (2026-04-17)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Performance
|
|
27
|
+
|
|
28
|
+
* minor optimition for index ([f6c4415](https://github.com/snowyu/events-ex.js/commit/f6c44157ffda17957fad5ad2e0aedbb8ec521be7))
|
|
29
|
+
|
|
5
30
|
## [2.3.0](https://github.com/snowyu/events-ex.js/compare/v2.2.0...v2.3.0) (2026-04-17)
|
|
6
31
|
|
|
7
32
|
|
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:
|
|
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 `
|
|
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"> </span>
|
|
83
|
+
<span class="cline-any cline-neutral"> </span>
|
|
84
|
+
<span class="cline-any cline-yes">21x</span>
|
|
85
|
+
<span class="cline-any cline-neutral"> </span>
|
|
86
|
+
<span class="cline-any cline-neutral"> </span>
|
|
87
|
+
<span class="cline-any cline-neutral"> </span>
|
|
88
|
+
<span class="cline-any cline-neutral"> </span>
|
|
89
|
+
<span class="cline-any cline-neutral"> </span>
|
|
90
|
+
<span class="cline-any cline-neutral"> </span>
|
|
91
|
+
<span class="cline-any cline-neutral"> </span>
|
|
92
|
+
<span class="cline-any cline-neutral"> </span>
|
|
93
|
+
<span class="cline-any cline-neutral"> </span>
|
|
94
|
+
<span class="cline-any cline-yes">2x</span>
|
|
95
|
+
<span class="cline-any cline-neutral"> </span>
|
|
96
|
+
<span class="cline-any cline-neutral"> </span>
|
|
97
|
+
<span class="cline-any cline-neutral"> </span>
|
|
98
|
+
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import eventable from './eventable'
|
|
99
|
+
|
|
100
|
+
const removeAllListeners = eventable().methods.removeAllListeners;
|
|
101
|
+
|
|
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
|
+
|
|
113
|
+
export default allOff;
|
|
114
|
+
</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
|
+
|