fastevent 1.0.0 → 1.0.1

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/readme_cn.md ADDED
@@ -0,0 +1,282 @@
1
+ # FastEvent
2
+
3
+ FastEvent 是一个功能强大的`TypeScript`事件管理库,提供了灵活的事件订阅和发布机制,支持事件通配符、作用域、异步事件等特性。
4
+
5
+ 对比`EventEmitter2`,`FastEvent`具有以下优势:
6
+
7
+ - 在含通配符发布与订阅时,`FastEvent`的性能比`EventEmitter2`高 `1+`倍左右。
8
+ - `FastEvent`包大小为 `6.3xkb`,而`EventEmitter2`为 `43.4kb`。
9
+ - `FastEvent`拥有更丰富的功能。
10
+
11
+ # 安装
12
+
13
+ 使用 npm 安装:
14
+
15
+ ```bash
16
+ npm install fastevent
17
+ ```
18
+
19
+ 或使用 yarn:
20
+
21
+ ```bash
22
+ yarn add fastevent
23
+ ```
24
+
25
+ # 快速入门
26
+
27
+ ## 基本使用
28
+
29
+ ```typescript
30
+ import { FastEvent } from 'fastevent';
31
+
32
+ // 创建事件实例
33
+ const events = new FastEvent();
34
+
35
+ // 订阅事件
36
+ events.on('user/login', (user) => {
37
+ console.log('用户登录:', user);
38
+ });
39
+
40
+ // 发布事件
41
+ events.emit('user/login', { id: 1, name: 'Alice' });
42
+ ```
43
+
44
+ # 指南
45
+
46
+ ## 事件通配符
47
+
48
+ FastEvent 支持两种通配符:
49
+ - `*`: 匹配单层路径
50
+ - `**`: 匹配多层路径
51
+
52
+ ```typescript
53
+ const events = new FastEvent();
54
+
55
+ // 匹配 user/*/login
56
+ events.on('user/*/login', (data) => {
57
+ console.log('任何用户类型的登录:', data);
58
+ });
59
+
60
+ // 匹配 user 下的所有事件
61
+ events.on('user/**', (data) => {
62
+ console.log('所有用户相关事件:', data);
63
+ });
64
+
65
+ // 触发事件
66
+ events.emit('user/admin/login', { id: 1 }); // 两个处理器都会被调用
67
+ events.emit('user/admin/profile/update', { name: 'New' }); // 只有 ** 处理器会被调用
68
+ ```
69
+
70
+ ## 事件作用域
71
+
72
+ 作用域允许你在特定的命名空间下处理事件:
73
+
74
+ ```typescript
75
+ const events = new FastEvent();
76
+
77
+ // 创建用户相关的作用域
78
+ const userScope = events.scope('user');
79
+
80
+ // 在作用域中订阅事件
81
+ userScope.on('login', (data) => {
82
+ console.log('用户登录:', data);
83
+ });
84
+
85
+ // 等同于 events.emit('user/login', data)
86
+ userScope.emit('login', { id: 1 });
87
+ ```
88
+
89
+ ## 一次性事件
90
+
91
+ 使用 `once` 订阅只触发一次的事件:
92
+
93
+ ```typescript
94
+ const events = new FastEvent();
95
+
96
+ events.once('startup', () => {
97
+ console.log('应用启动');
98
+ });
99
+
100
+ events.emit('startup'); // 输出: 应用启动
101
+ events.emit('startup'); // 无输出,监听器已被移除
102
+ ```
103
+
104
+ ## 异步事件
105
+
106
+ 支持异步事件处理:
107
+
108
+ ```typescript
109
+ const events = new FastEvent();
110
+
111
+ events.on('data/fetch', async () => {
112
+ const response = await fetch('https://api.example.com/data');
113
+ return await response.json();
114
+ });
115
+
116
+ // 异步发布事件
117
+ const results = await events.emitAsync('data/fetch');
118
+ console.log('所有处理器的结果:', results);
119
+ ```
120
+
121
+ ## 事件等待
122
+
123
+ 使用 `waitFor` 等待特定事件发生:
124
+
125
+ ```typescript
126
+ const events = new FastEvent();
127
+
128
+ async function waitForLogin() {
129
+ try {
130
+ // 等待登录事件,超时时间 5 秒
131
+ const userData = await events.waitFor('user/login', 5000);
132
+ console.log('用户已登录:', userData);
133
+ } catch (error) {
134
+ console.log('登录等待超时');
135
+ }
136
+ }
137
+
138
+ waitForLogin();
139
+ // 稍后触发登录事件
140
+ events.emit('user/login', { id: 1, name: 'Alice' });
141
+ ```
142
+
143
+ ## 保留事件数据
144
+
145
+ 保留最后一次事件数据,新的订阅者会立即收到该数据:
146
+
147
+ ```typescript
148
+ const events = new FastEvent();
149
+
150
+ // 发布事件并保留
151
+ events.emit('config/update', { theme: 'dark' }, true);
152
+
153
+ // 之后的订阅者会立即收到保留的数据
154
+ events.on('config/update', (config) => {
155
+ console.log('配置:', config); // 立即输出: 配置: { theme: 'dark' }
156
+ });
157
+ ```
158
+
159
+ ## 多级事件
160
+
161
+ 支持发布和订阅多级事件。
162
+
163
+ 默认使用 `/` 作为事件路径分隔符,你也可以使用自定义的分隔符:
164
+
165
+ ```typescript
166
+ const events = new FastEvent({
167
+ delimiter: '.'
168
+ });
169
+ ```
170
+
171
+ ## 全局事件监听
172
+
173
+ 使用 `onAny` 监听所有事件:
174
+
175
+ ```typescript
176
+ const events = new FastEvent();
177
+
178
+ events.onAny((data, meta) => {
179
+ console.log(`事件 ${meta.type} 被触发:`, data);
180
+ });
181
+
182
+ events.emit('user/login', { id: 1 }); // 输出: 事件 user/login 被触发: { id: 1 }
183
+ events.emit('system/error', 'Connection failed'); // 输出: 事件 system/error 被触发: Connection failed
184
+ ```
185
+
186
+ ## 元数据(Meta)
187
+
188
+ 元数据是一种为事件提供额外上下文信息的机制。你可以在全局范围内设置元数据,也可以为单个事件添加特定的元数据。
189
+
190
+ ### 全局元数据
191
+
192
+ 在创建 FastEvent 实例时设置全局元数据:
193
+
194
+ ```typescript
195
+ const events = new FastEvent({
196
+ meta: {
197
+ version: '1.0',
198
+ environment: 'production'
199
+ }
200
+ });
201
+
202
+ events.on('user/login', (data, meta) => {
203
+ console.log('事件数据:', data);
204
+ console.log('元数据:', meta); // 包含 type、version 和 environment
205
+ });
206
+ ```
207
+
208
+ ### 事件特定元数据
209
+
210
+ 在发布事件时可以传递额外的元数据,它会与全局元数据合并:
211
+
212
+ ```typescript
213
+ const events = new FastEvent({
214
+ meta: { app: 'MyApp' }
215
+ });
216
+
217
+ // 在发布事件时添加特定的元数据
218
+ events.emit('order/create',
219
+ { orderId: '123' }, // 事件数据
220
+ false, // 不保留
221
+ { timestamp: Date.now() } // 事件特定的元数据
222
+ );
223
+
224
+ // 监听器接收合并后的元数据
225
+ events.on('order/create', (data, meta) => {
226
+ console.log('订单:', data); // { orderId: '123' }
227
+ console.log('元数据:', meta); // { type: 'order/create', app: 'MyApp', timestamp: ... }
228
+ });
229
+ ```
230
+
231
+ ## 错误处理
232
+
233
+ FastEvent 提供了错误处理机制:
234
+
235
+ ```typescript
236
+ const events = new FastEvent({
237
+ ignoreErrors: true, // 默认为 true,不会抛出错误
238
+ onListenerError: (type, error) => {
239
+ console.error(`处理事件 ${type} 时发生错误:`, error);
240
+ }
241
+ });
242
+
243
+ events.on('process', () => {
244
+ throw new Error('处理失败');
245
+ });
246
+
247
+ // 不会抛出错误,而是触发 onListenerError
248
+ events.emit('process');
249
+ ```
250
+
251
+ ## 自定义选项
252
+
253
+ FastEvent 构造函数支持多个选项:
254
+
255
+ ```typescript
256
+ const events = new FastEvent({
257
+ // 事件路径分隔符,默认为 '/'
258
+ delimiter: '.',
259
+ // 事件处理器的上下文
260
+ context: null,
261
+ // 元数据,会传递给所有事件处理器
262
+ meta: { ... },
263
+
264
+ // 错误处理
265
+ ignoreErrors: true,
266
+ onListenerError: (type, error) => {
267
+ console.error(`事件错误:`, type, error);
268
+ },
269
+
270
+ // 监听器添加/移除的回调
271
+ onAddListener: (path, listener) => {
272
+ console.log('添加监听器:', path);
273
+ },
274
+ onRemoveListener: (path, listener) => {
275
+ console.log('移除监听器:', path);
276
+ }
277
+ });
278
+ ```
279
+
280
+ # 性能
281
+
282
+ ![](./bench.png)
@@ -77,5 +77,18 @@ describe("简单发布与订阅",async ()=>{
77
77
  expect(err._trigger).toBe("x")
78
78
 
79
79
  })
80
+ test("添加侦听器时指定顺序",async ()=>{
81
+ const emitter = new FastEvent()
82
+ const types:number[]=[]
83
+ emitter.on("x",()=>{
84
+ types.push(1)
85
+ })
86
+
87
+ emitter.on("x",()=>{
88
+ types.push(2)
89
+ },{prepend:true})
90
+ emitter.emit("x")
91
+ expect(types).toEqual([2,1])
92
+ })
80
93
  })
81
94
 
@@ -9,7 +9,7 @@ describe("指定次数事件的发布与订阅",async ()=>{
9
9
  expect(type).toBe("x")
10
10
  expect(payload).toBe(1)
11
11
  events.push(type)
12
- },2)
12
+ },{count:2})
13
13
  emitter.emit("x",1)
14
14
  emitter.emit("x",1)
15
15
  emitter.emit("x",1)
package/src/event.ts CHANGED
@@ -7,7 +7,8 @@ import {
7
7
  FastListenerNode,
8
8
  FastEventSubscriber,
9
9
  ScopeEvents,
10
- FastEventMeta
10
+ FastEventMeta,
11
+ FastEventListenOptions
11
12
  } from './types';
12
13
  import { isPathMatched } from './utils/isPathMatched';
13
14
  import { removeItem } from './utils/removeItem';
@@ -32,9 +33,15 @@ export class FastEvent<
32
33
  this._context = this._options.context || this
33
34
  }
34
35
  get options(){ return this._options }
35
- private _addListener(parts:string[],listener:FastEventListener<any,any>,count?:number):FastListenerNode | undefined{
36
+ private _addListener(parts:string[],listener:FastEventListener<any,any>,options:Required<FastEventListenOptions>):FastListenerNode | undefined{
37
+ const { count, prepend } = options
36
38
  return this._forEachNodes(parts,(node)=>{
37
- node.__listeners.push(count && count >0 ? [listener,count]: listener)
39
+ const newListener = count >0 ? [listener,count]: listener as any
40
+ if(prepend){
41
+ node.__listeners.splice(0,0,newListener)
42
+ }else{
43
+ node.__listeners.push(newListener)
44
+ }
38
45
  if(typeof(this._options.onAddListener)==='function'){
39
46
  this._options.onAddListener(parts,listener)
40
47
  }
@@ -88,13 +95,17 @@ export class FastEvent<
88
95
  return isRemove
89
96
  })
90
97
  }
91
- public on<T extends string>(type: T, listener: FastEventListener<T,Events[T],Meta>, count?:number ): FastEventSubscriber
92
- public on<T extends Types=Types>(type: T, listener: FastEventListener<Types,Events[T],Meta>, count?:number ): FastEventSubscriber
98
+ public on<T extends string>(type: T, listener: FastEventListener<T,Events[T],Meta>, options?:FastEventListenOptions ): FastEventSubscriber
99
+ public on<T extends Types=Types>(type: T, listener: FastEventListener<Types,Events[T],Meta>, options?:FastEventListenOptions): FastEventSubscriber
93
100
  public on<P=any>(type: '**', listener: FastEventListener<Types,P,Meta>): FastEventSubscriber
94
101
  public on(): FastEventSubscriber{
95
102
  const type = arguments[0] as string
96
103
  const listener = arguments[1] as FastEventListener
97
- const count = arguments[2] as number
104
+ const options = Object.assign({
105
+ count : 0,
106
+ prepend: false
107
+ },arguments[2]) as Required<FastEventListenOptions>
108
+
98
109
  if(type.length===0) throw new Error('event type cannot be empty')
99
110
 
100
111
  if(type==='**'){
@@ -102,7 +113,7 @@ export class FastEvent<
102
113
  }
103
114
 
104
115
  const parts = type.split(this._delimiter);
105
- const node = this._addListener(parts,listener,count)
116
+ const node = this._addListener(parts,listener,options)
106
117
 
107
118
  // Retain不支持通配符
108
119
  if(node && !type.includes('*')) this._emitForLastEvent(type)
@@ -115,7 +126,7 @@ export class FastEvent<
115
126
  public once<T extends string>(type: T, listener: FastEventListener<T,Events[T],Meta> ): FastEventSubscriber
116
127
  public once<T extends Types=Types>(type: T, listener: FastEventListener<Types,Events[T],Meta> ): FastEventSubscriber
117
128
  public once(): FastEventSubscriber{
118
- return this.on(arguments[0],arguments[1],1)
129
+ return this.on(arguments[0],arguments[1],{count:1})
119
130
  }
120
131
 
121
132
  /**
@@ -132,9 +143,13 @@ export class FastEvent<
132
143
  * subscriber.off();
133
144
  * ```
134
145
  */
135
- onAny<P=any>(listener: FastEventListener<string,P,Meta>): FastEventSubscriber {
146
+ onAny<P=any>(listener: FastEventListener<string,P,Meta>, options?:Pick<FastEventListenOptions,'prepend'>): FastEventSubscriber {
136
147
  const listeners = this.listeners.__listeners
137
- listeners.push(listener)
148
+ if(options && options.prepend){
149
+ listeners.splice(0,0,listener)
150
+ }else{
151
+ listeners.push(listener)
152
+ }
138
153
  return {
139
154
  off:()=>this._removeListener(this.listeners,[],listener)
140
155
  }
package/src/scope.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FastEvent } from "./event";
2
- import { FastEventListener, FastEvents, FastEventSubscriber } from "./types";
2
+ import { FastEventListener, FastEventListenOptions, FastEvents, FastEventSubscriber } from "./types";
3
3
 
4
4
  export class FastEventScope<
5
5
  Events extends FastEvents = FastEvents,
@@ -29,8 +29,8 @@ export class FastEventScope<
29
29
  return type===undefined ? undefined : this.prefix + type
30
30
  }
31
31
 
32
- public on<T extends string>(type: T, listener: FastEventListener<T,Events[T],Meta>, count?:number ): FastEventSubscriber
33
- public on<T extends Types=Types>(type: T, listener: FastEventListener<T,Events[T],Meta>, count?:number ): FastEventSubscriber
32
+ public on<T extends string>(type: T, listener: FastEventListener<T,Events[T],Meta>, options?:FastEventListenOptions ): FastEventSubscriber
33
+ public on<T extends Types=Types>(type: T, listener: FastEventListener<T,Events[T],Meta>, options?:FastEventListenOptions): FastEventSubscriber
34
34
  public on(type: '**', listener: FastEventListener<any,any,Meta>): FastEventSubscriber
35
35
  public on(): FastEventSubscriber{
36
36
  const args = [...arguments] as [any,any,any]
@@ -39,15 +39,15 @@ export class FastEventScope<
39
39
  return this.emitter.on(...args)
40
40
  }
41
41
 
42
- public once<T extends string>(type: T, listener: FastEventListener<T,Events[T],Meta> ): FastEventSubscriber
43
- public once<T extends Types = Types>(type: T, listener: FastEventListener<Types,Events[T],Meta> ): FastEventSubscriber
42
+ public once<T extends string>(type: T, listener: FastEventListener<T,Events[T],Meta>, options?:FastEventListenOptions ): FastEventSubscriber
43
+ public once<T extends Types = Types>(type: T, listener: FastEventListener<Types,Events[T],Meta>, options?:FastEventListenOptions ): FastEventSubscriber
44
44
  public once(): FastEventSubscriber{
45
- return this.on(arguments[0],arguments[1],1)
45
+ return this.on(arguments[0],arguments[1],Object.assign(arguments[2],{},{count:1}))
46
46
  }
47
47
 
48
- onAny<P=any>(listener: FastEventListener<Types,P,Meta>): FastEventSubscriber {
48
+ onAny<P=any>(listener: FastEventListener<Types,P,Meta>, options?:FastEventListenOptions): FastEventSubscriber {
49
49
  const type = this.prefix + '**'
50
- return this.on(type as any,listener)
50
+ return this.on(type as any,listener,options)
51
51
  }
52
52
  offAll(){
53
53
  this.emitter.offAll(this.prefix)
package/src/types.ts CHANGED
@@ -48,3 +48,10 @@ export type ScopeEvents<T extends Record<string, any>, Prefix extends string> =
48
48
  };
49
49
 
50
50
 
51
+
52
+ export type FastEventListenOptions={
53
+ // 侦听执行次数,当为1时为单次侦听,为0时为永久侦听,其他值为执行次数,每执行一次减一,减到0时移除侦听器
54
+ count?:number
55
+ // 将侦听器添加到侦听器列表的头部
56
+ prepend?:boolean
57
+ }