emittery 0.5.1 → 0.7.2
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/index.d.ts +195 -25
- package/index.js +109 -51
- package/license +1 -1
- package/package.json +9 -7
- package/readme.md +126 -23
package/index.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Emittery accepts strings and symbols as event names.
|
|
3
|
+
|
|
4
|
+
Symbol event names can be used to avoid name collisions when your classes are extended, especially for internal events.
|
|
5
|
+
*/
|
|
6
|
+
type EventName = string | symbol;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
Emittery also accepts an array of strings and symbols as event names.
|
|
10
|
+
*/
|
|
11
|
+
type EventNames = EventName | readonly EventName[];
|
|
12
|
+
|
|
1
13
|
declare class Emittery {
|
|
2
14
|
/**
|
|
3
15
|
In TypeScript, it returns a decorator which mixins `Emittery` as property `emitteryPropertyName` and `methodNames`, or all `Emittery` methods if `methodNames` is not defined, into the target class.
|
|
@@ -14,16 +26,88 @@ declare class Emittery {
|
|
|
14
26
|
instance.emit('event');
|
|
15
27
|
```
|
|
16
28
|
*/
|
|
17
|
-
static mixin(emitteryPropertyName: string, methodNames?: readonly string[]): Function;
|
|
29
|
+
static mixin(emitteryPropertyName: string | symbol, methodNames?: readonly string[]): Function;
|
|
18
30
|
|
|
19
31
|
/**
|
|
20
|
-
|
|
32
|
+
Fires when an event listener was added.
|
|
33
|
+
|
|
34
|
+
An object with `listener` and `eventName` (if `on` or `off` was used) is provided as event data.
|
|
35
|
+
|
|
36
|
+
@example
|
|
37
|
+
```
|
|
38
|
+
import Emittery = require('emittery');
|
|
39
|
+
|
|
40
|
+
const emitter = new Emittery();
|
|
41
|
+
|
|
42
|
+
emitter.on(Emittery.listenerAdded, ({listener, eventName}) => {
|
|
43
|
+
console.log(listener);
|
|
44
|
+
//=> data => {}
|
|
45
|
+
|
|
46
|
+
console.log(eventName);
|
|
47
|
+
//=> '🦄'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
emitter.on('🦄', data => {
|
|
51
|
+
// Handle data
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
*/
|
|
55
|
+
static readonly listenerAdded: unique symbol;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
Fires when an event listener was removed.
|
|
59
|
+
|
|
60
|
+
An object with `listener` and `eventName` (if `on` or `off` was used) is provided as event data.
|
|
61
|
+
|
|
62
|
+
@example
|
|
63
|
+
```
|
|
64
|
+
import Emittery = require('emittery');
|
|
65
|
+
|
|
66
|
+
const emitter = new Emittery();
|
|
67
|
+
|
|
68
|
+
const off = emitter.on('🦄', data => {
|
|
69
|
+
// Handle data
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
emitter.on(Emittery.listenerRemoved, ({listener, eventName}) => {
|
|
73
|
+
console.log(listener);
|
|
74
|
+
//=> data => {}
|
|
75
|
+
|
|
76
|
+
console.log(eventName);
|
|
77
|
+
//=> '🦄'
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
off();
|
|
81
|
+
```
|
|
82
|
+
*/
|
|
83
|
+
static readonly listenerRemoved: unique symbol;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
Subscribe to one or more events.
|
|
21
87
|
|
|
22
88
|
Using the same listener multiple times for the same event will result in only one method call per emitted event.
|
|
23
89
|
|
|
24
90
|
@returns An unsubscribe method.
|
|
91
|
+
|
|
92
|
+
@example
|
|
93
|
+
```
|
|
94
|
+
import Emittery = require('emittery');
|
|
95
|
+
|
|
96
|
+
const emitter = new Emittery();
|
|
97
|
+
|
|
98
|
+
emitter.on('🦄', data => {
|
|
99
|
+
console.log(data);
|
|
100
|
+
});
|
|
101
|
+
emitter.on(['🦄', '🐶'], data => {
|
|
102
|
+
console.log(data);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
emitter.emit('🦄', '🌈'); // log => '🌈' x2
|
|
106
|
+
emitter.emit('🐶', '🍖'); // log => '🍖'
|
|
107
|
+
```
|
|
25
108
|
*/
|
|
26
|
-
on(eventName:
|
|
109
|
+
on(eventName: typeof Emittery.listenerAdded | typeof Emittery.listenerRemoved, listener: (eventData: Emittery.ListenerChangedData) => void): Emittery.UnsubscribeFn
|
|
110
|
+
on(eventName: EventNames, listener: (eventData?: unknown) => void): Emittery.UnsubscribeFn;
|
|
27
111
|
|
|
28
112
|
/**
|
|
29
113
|
Get an async iterator which buffers data each time an event is emitted.
|
|
@@ -77,28 +161,97 @@ declare class Emittery {
|
|
|
77
161
|
}
|
|
78
162
|
}
|
|
79
163
|
```
|
|
164
|
+
|
|
165
|
+
It accepts multiple event names.
|
|
166
|
+
|
|
167
|
+
@example
|
|
168
|
+
```
|
|
169
|
+
import Emittery = require('emittery');
|
|
170
|
+
|
|
171
|
+
const emitter = new Emittery();
|
|
172
|
+
const iterator = emitter.events(['🦄', '🦊']);
|
|
173
|
+
|
|
174
|
+
emitter.emit('🦄', '🌈1'); // Buffered
|
|
175
|
+
emitter.emit('🦊', '🌈2'); // Buffered
|
|
176
|
+
|
|
177
|
+
iterator
|
|
178
|
+
.next()
|
|
179
|
+
.then(({value, done}) => {
|
|
180
|
+
// done === false
|
|
181
|
+
// value === '🌈1'
|
|
182
|
+
return iterator.next();
|
|
183
|
+
})
|
|
184
|
+
.then(({value, done}) => {
|
|
185
|
+
// done === false
|
|
186
|
+
// value === '🌈2'
|
|
187
|
+
// Revoke subscription
|
|
188
|
+
return iterator.return();
|
|
189
|
+
})
|
|
190
|
+
.then(({done}) => {
|
|
191
|
+
// done === true
|
|
192
|
+
});
|
|
193
|
+
```
|
|
80
194
|
*/
|
|
81
|
-
events(eventName:
|
|
195
|
+
events(eventName: EventNames): AsyncIterableIterator<unknown>
|
|
82
196
|
|
|
83
197
|
/**
|
|
84
|
-
Remove
|
|
198
|
+
Remove one or more event subscriptions.
|
|
199
|
+
|
|
200
|
+
@example
|
|
201
|
+
```
|
|
202
|
+
import Emittery = require('emittery');
|
|
203
|
+
|
|
204
|
+
const emitter = new Emittery();
|
|
205
|
+
|
|
206
|
+
const listener = data => console.log(data);
|
|
207
|
+
(async () => {
|
|
208
|
+
emitter.on(['🦄', '🐶', '🦊'], listener);
|
|
209
|
+
await emitter.emit('🦄', 'a');
|
|
210
|
+
await emitter.emit('🐶', 'b');
|
|
211
|
+
await emitter.emit('🦊', 'c');
|
|
212
|
+
emitter.off('🦄', listener);
|
|
213
|
+
emitter.off(['🐶', '🦊'], listener);
|
|
214
|
+
await emitter.emit('🦄', 'a'); // nothing happens
|
|
215
|
+
await emitter.emit('🐶', 'b'); // nothing happens
|
|
216
|
+
await emitter.emit('🦊', 'c'); // nothing happens
|
|
217
|
+
})();
|
|
218
|
+
```
|
|
85
219
|
*/
|
|
86
|
-
off(eventName:
|
|
220
|
+
off(eventName: EventNames, listener: (eventData?: unknown) => void): void;
|
|
87
221
|
|
|
88
222
|
/**
|
|
89
|
-
Subscribe to
|
|
223
|
+
Subscribe to one or more events only once. It will be unsubscribed after the first
|
|
90
224
|
event.
|
|
91
225
|
|
|
92
226
|
@returns The event data when `eventName` is emitted.
|
|
227
|
+
|
|
228
|
+
@example
|
|
229
|
+
```
|
|
230
|
+
import Emittery = require('emittery');
|
|
231
|
+
|
|
232
|
+
const emitter = new Emittery();
|
|
233
|
+
|
|
234
|
+
emitter.once('🦄').then(data => {
|
|
235
|
+
console.log(data);
|
|
236
|
+
//=> '🌈'
|
|
237
|
+
});
|
|
238
|
+
emitter.once(['🦄', '🐶']).then(data => {
|
|
239
|
+
console.log(data);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
emitter.emit('🦄', '🌈'); // Logs `🌈` twice
|
|
243
|
+
emitter.emit('🐶', '🍖'); // Nothing happens
|
|
244
|
+
```
|
|
93
245
|
*/
|
|
94
|
-
once(eventName:
|
|
246
|
+
once(eventName: typeof Emittery.listenerAdded | typeof Emittery.listenerRemoved): Promise<Emittery.ListenerChangedData>
|
|
247
|
+
once(eventName: EventNames): Promise<unknown>;
|
|
95
248
|
|
|
96
249
|
/**
|
|
97
250
|
Trigger an event asynchronously, optionally with some data. Listeners are called in the order they were added, but executed concurrently.
|
|
98
251
|
|
|
99
252
|
@returns A promise that resolves when all the event listeners are done. *Done* meaning executed if synchronous or resolved when an async/promise-returning function. You usually wouldn't want to wait for this, but you could for example catch possible errors. If any of the listeners throw/reject, the returned promise will be rejected with the error, but the other listeners will not be affected.
|
|
100
253
|
*/
|
|
101
|
-
emit(eventName:
|
|
254
|
+
emit(eventName: EventName, eventData?: unknown): Promise<void>;
|
|
102
255
|
|
|
103
256
|
/**
|
|
104
257
|
Same as `emit()`, but it waits for each listener to resolve before triggering the next one. This can be useful if your events depend on each other. Although ideally they should not. Prefer `emit()` whenever possible.
|
|
@@ -107,14 +260,14 @@ declare class Emittery {
|
|
|
107
260
|
|
|
108
261
|
@returns A promise that resolves when all the event listeners are done.
|
|
109
262
|
*/
|
|
110
|
-
emitSerial(eventName:
|
|
263
|
+
emitSerial(eventName: EventName, eventData?: unknown): Promise<void>;
|
|
111
264
|
|
|
112
265
|
/**
|
|
113
266
|
Subscribe to be notified about any event.
|
|
114
267
|
|
|
115
268
|
@returns A method to unsubscribe.
|
|
116
269
|
*/
|
|
117
|
-
onAny(listener: (eventName:
|
|
270
|
+
onAny(listener: (eventName: EventName, eventData?: unknown) => unknown): Emittery.UnsubscribeFn;
|
|
118
271
|
|
|
119
272
|
/**
|
|
120
273
|
Get an async iterator which buffers a tuple of an event name and data each time an event is emitted.
|
|
@@ -155,19 +308,19 @@ declare class Emittery {
|
|
|
155
308
|
/**
|
|
156
309
|
Remove an `onAny` subscription.
|
|
157
310
|
*/
|
|
158
|
-
offAny(listener: (eventName:
|
|
311
|
+
offAny(listener: (eventName: EventName, eventData?: unknown) => void): void;
|
|
159
312
|
|
|
160
313
|
/**
|
|
161
314
|
Clear all event listeners on the instance.
|
|
162
315
|
|
|
163
316
|
If `eventName` is given, only the listeners for that event are cleared.
|
|
164
317
|
*/
|
|
165
|
-
clearListeners(eventName?:
|
|
318
|
+
clearListeners(eventName?: EventNames): void;
|
|
166
319
|
|
|
167
320
|
/**
|
|
168
321
|
The number of listeners for the `eventName` or all events if not specified.
|
|
169
322
|
*/
|
|
170
|
-
listenerCount(eventName?:
|
|
323
|
+
listenerCount(eventName?: EventNames): number;
|
|
171
324
|
|
|
172
325
|
/**
|
|
173
326
|
Bind the given `methodNames`, or all `Emittery` methods if `methodNames` is not defined, into the `target` object.
|
|
@@ -191,12 +344,29 @@ declare namespace Emittery {
|
|
|
191
344
|
Removes an event subscription.
|
|
192
345
|
*/
|
|
193
346
|
type UnsubscribeFn = () => void;
|
|
347
|
+
type EventNameFromDataMap<EventDataMap> = Extract<keyof EventDataMap, EventName>;
|
|
194
348
|
|
|
195
349
|
/**
|
|
196
350
|
Maps event names to their emitted data type.
|
|
197
351
|
*/
|
|
198
352
|
interface Events {
|
|
199
|
-
|
|
353
|
+
// Blocked by https://github.com/microsoft/TypeScript/issues/1863, should be
|
|
354
|
+
// `[eventName: EventName]: unknown;`
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
The data provided as `eventData` when listening for `Emittery.listenerAdded` or `Emittery.listenerRemoved`.
|
|
359
|
+
*/
|
|
360
|
+
interface ListenerChangedData {
|
|
361
|
+
/**
|
|
362
|
+
The listener that was added or removed.
|
|
363
|
+
*/
|
|
364
|
+
listener: (eventData?: unknown) => void;
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
The name of the event that was added or removed if `.on()` or `.off()` was used, or `undefined` if `.onAny()` or `.offAny()` was used.
|
|
368
|
+
*/
|
|
369
|
+
eventName?: EventName;
|
|
200
370
|
}
|
|
201
371
|
|
|
202
372
|
/**
|
|
@@ -216,27 +386,27 @@ declare namespace Emittery {
|
|
|
216
386
|
emitter.emit('end'); // TS compilation error
|
|
217
387
|
```
|
|
218
388
|
*/
|
|
219
|
-
class Typed<EventDataMap extends Events, EmptyEvents extends
|
|
220
|
-
on<Name extends
|
|
389
|
+
class Typed<EventDataMap extends Events, EmptyEvents extends EventName = never> extends Emittery {
|
|
390
|
+
on<Name extends EventNameFromDataMap<EventDataMap>>(eventName: Name, listener: (eventData: EventDataMap[Name]) => void): Emittery.UnsubscribeFn;
|
|
221
391
|
on<Name extends EmptyEvents>(eventName: Name, listener: () => void): Emittery.UnsubscribeFn;
|
|
222
392
|
|
|
223
|
-
events<Name extends
|
|
393
|
+
events<Name extends EventNameFromDataMap<EventDataMap>>(eventName: Name): AsyncIterableIterator<EventDataMap[Name]>;
|
|
224
394
|
|
|
225
|
-
once<Name extends
|
|
395
|
+
once<Name extends EventNameFromDataMap<EventDataMap>>(eventName: Name): Promise<EventDataMap[Name]>;
|
|
226
396
|
once<Name extends EmptyEvents>(eventName: Name): Promise<void>;
|
|
227
397
|
|
|
228
|
-
off<Name extends
|
|
398
|
+
off<Name extends EventNameFromDataMap<EventDataMap>>(eventName: Name, listener: (eventData: EventDataMap[Name]) => void): void;
|
|
229
399
|
off<Name extends EmptyEvents>(eventName: Name, listener: () => void): void;
|
|
230
400
|
|
|
231
|
-
onAny(listener: (eventName:
|
|
232
|
-
anyEvent(): AsyncIterableIterator<[
|
|
401
|
+
onAny(listener: (eventName: EventNameFromDataMap<EventDataMap> | EmptyEvents, eventData?: EventDataMap[EventNameFromDataMap<EventDataMap>]) => void): Emittery.UnsubscribeFn;
|
|
402
|
+
anyEvent(): AsyncIterableIterator<[EventNameFromDataMap<EventDataMap>, EventDataMap[EventNameFromDataMap<EventDataMap>]]>;
|
|
233
403
|
|
|
234
|
-
offAny(listener: (eventName:
|
|
404
|
+
offAny(listener: (eventName: EventNameFromDataMap<EventDataMap> | EmptyEvents, eventData?: EventDataMap[EventNameFromDataMap<EventDataMap>]) => void): void;
|
|
235
405
|
|
|
236
|
-
emit<Name extends
|
|
406
|
+
emit<Name extends EventNameFromDataMap<EventDataMap>>(eventName: Name, eventData: EventDataMap[Name]): Promise<void>;
|
|
237
407
|
emit<Name extends EmptyEvents>(eventName: Name): Promise<void>;
|
|
238
408
|
|
|
239
|
-
emitSerial<Name extends
|
|
409
|
+
emitSerial<Name extends EventNameFromDataMap<EventDataMap>>(eventName: Name, eventData: EventDataMap[Name]): Promise<void>;
|
|
240
410
|
emitSerial<Name extends EmptyEvents>(eventName: Name): Promise<void>;
|
|
241
411
|
}
|
|
242
412
|
}
|
package/index.js
CHANGED
|
@@ -6,9 +6,12 @@ const producersMap = new WeakMap();
|
|
|
6
6
|
const anyProducer = Symbol('anyProducer');
|
|
7
7
|
const resolvedPromise = Promise.resolve();
|
|
8
8
|
|
|
9
|
+
const listenerAdded = Symbol('listenerAdded');
|
|
10
|
+
const listenerRemoved = Symbol('listenerRemoved');
|
|
11
|
+
|
|
9
12
|
function assertEventName(eventName) {
|
|
10
|
-
if (typeof eventName !== 'string') {
|
|
11
|
-
throw new TypeError('eventName must be a string');
|
|
13
|
+
if (typeof eventName !== 'string' && typeof eventName !== 'symbol') {
|
|
14
|
+
throw new TypeError('eventName must be a string or a symbol');
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
17
|
|
|
@@ -28,7 +31,7 @@ function getListeners(instance, eventName) {
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
function getEventProducers(instance, eventName) {
|
|
31
|
-
const key = typeof eventName === 'string' ? eventName : anyProducer;
|
|
34
|
+
const key = typeof eventName === 'string' || typeof eventName === 'symbol' ? eventName : anyProducer;
|
|
32
35
|
const producers = producersMap.get(instance);
|
|
33
36
|
if (!producers.has(key)) {
|
|
34
37
|
producers.set(key, new Set());
|
|
@@ -53,7 +56,9 @@ function enqueueProducers(instance, eventName, eventData) {
|
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
function iterator(instance,
|
|
59
|
+
function iterator(instance, eventNames) {
|
|
60
|
+
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
|
|
61
|
+
|
|
57
62
|
let isFinished = false;
|
|
58
63
|
let flush = () => {};
|
|
59
64
|
let queue = [];
|
|
@@ -69,7 +74,9 @@ function iterator(instance, eventName) {
|
|
|
69
74
|
}
|
|
70
75
|
};
|
|
71
76
|
|
|
72
|
-
|
|
77
|
+
for (const eventName of eventNames) {
|
|
78
|
+
getEventProducers(instance, eventName).add(producer);
|
|
79
|
+
}
|
|
73
80
|
|
|
74
81
|
return {
|
|
75
82
|
async next() {
|
|
@@ -98,7 +105,11 @@ function iterator(instance, eventName) {
|
|
|
98
105
|
|
|
99
106
|
async return(value) {
|
|
100
107
|
queue = undefined;
|
|
101
|
-
|
|
108
|
+
|
|
109
|
+
for (const eventName of eventNames) {
|
|
110
|
+
getEventProducers(instance, eventName).delete(producer);
|
|
111
|
+
}
|
|
112
|
+
|
|
102
113
|
flush();
|
|
103
114
|
|
|
104
115
|
return arguments.length > 0 ?
|
|
@@ -134,6 +145,8 @@ function defaultMethodNamesOrAssert(methodNames) {
|
|
|
134
145
|
return methodNames;
|
|
135
146
|
}
|
|
136
147
|
|
|
148
|
+
const isListenerSymbol = symbol => symbol === listenerAdded || symbol === listenerRemoved;
|
|
149
|
+
|
|
137
150
|
class Emittery {
|
|
138
151
|
static mixin(emitteryPropertyName, methodNames) {
|
|
139
152
|
methodNames = defaultMethodNamesOrAssert(methodNames);
|
|
@@ -182,32 +195,52 @@ class Emittery {
|
|
|
182
195
|
producersMap.set(this, new Map());
|
|
183
196
|
}
|
|
184
197
|
|
|
185
|
-
on(
|
|
186
|
-
assertEventName(eventName);
|
|
198
|
+
on(eventNames, listener) {
|
|
187
199
|
assertListener(listener);
|
|
188
|
-
|
|
189
|
-
|
|
200
|
+
|
|
201
|
+
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
|
|
202
|
+
for (const eventName of eventNames) {
|
|
203
|
+
assertEventName(eventName);
|
|
204
|
+
getListeners(this, eventName).add(listener);
|
|
205
|
+
|
|
206
|
+
if (!isListenerSymbol(eventName)) {
|
|
207
|
+
this.emit(listenerAdded, {eventName, listener});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return this.off.bind(this, eventNames, listener);
|
|
190
212
|
}
|
|
191
213
|
|
|
192
|
-
off(
|
|
193
|
-
assertEventName(eventName);
|
|
214
|
+
off(eventNames, listener) {
|
|
194
215
|
assertListener(listener);
|
|
195
|
-
|
|
216
|
+
|
|
217
|
+
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
|
|
218
|
+
for (const eventName of eventNames) {
|
|
219
|
+
assertEventName(eventName);
|
|
220
|
+
getListeners(this, eventName).delete(listener);
|
|
221
|
+
|
|
222
|
+
if (!isListenerSymbol(eventName)) {
|
|
223
|
+
this.emit(listenerRemoved, {eventName, listener});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
196
226
|
}
|
|
197
227
|
|
|
198
|
-
once(
|
|
228
|
+
once(eventNames) {
|
|
199
229
|
return new Promise(resolve => {
|
|
200
|
-
|
|
201
|
-
const off = this.on(eventName, data => {
|
|
230
|
+
const off = this.on(eventNames, data => {
|
|
202
231
|
off();
|
|
203
232
|
resolve(data);
|
|
204
233
|
});
|
|
205
234
|
});
|
|
206
235
|
}
|
|
207
236
|
|
|
208
|
-
events(
|
|
209
|
-
|
|
210
|
-
|
|
237
|
+
events(eventNames) {
|
|
238
|
+
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
|
|
239
|
+
for (const eventName of eventNames) {
|
|
240
|
+
assertEventName(eventName);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return iterator(this, eventNames);
|
|
211
244
|
}
|
|
212
245
|
|
|
213
246
|
async emit(eventName, eventData) {
|
|
@@ -218,10 +251,10 @@ class Emittery {
|
|
|
218
251
|
const listeners = getListeners(this, eventName);
|
|
219
252
|
const anyListeners = anyMap.get(this);
|
|
220
253
|
const staticListeners = [...listeners];
|
|
221
|
-
const staticAnyListeners = [...anyListeners];
|
|
254
|
+
const staticAnyListeners = isListenerSymbol(eventName) ? [] : [...anyListeners];
|
|
222
255
|
|
|
223
256
|
await resolvedPromise;
|
|
224
|
-
|
|
257
|
+
await Promise.all([
|
|
225
258
|
...staticListeners.map(async listener => {
|
|
226
259
|
if (listeners.has(listener)) {
|
|
227
260
|
return listener(eventData);
|
|
@@ -262,6 +295,7 @@ class Emittery {
|
|
|
262
295
|
onAny(listener) {
|
|
263
296
|
assertListener(listener);
|
|
264
297
|
anyMap.get(this).add(listener);
|
|
298
|
+
this.emit(listenerAdded, {listener});
|
|
265
299
|
return this.offAny.bind(this, listener);
|
|
266
300
|
}
|
|
267
301
|
|
|
@@ -271,55 +305,66 @@ class Emittery {
|
|
|
271
305
|
|
|
272
306
|
offAny(listener) {
|
|
273
307
|
assertListener(listener);
|
|
308
|
+
this.emit(listenerRemoved, {listener});
|
|
274
309
|
anyMap.get(this).delete(listener);
|
|
275
310
|
}
|
|
276
311
|
|
|
277
|
-
clearListeners(
|
|
278
|
-
|
|
279
|
-
getListeners(this, eventName).clear();
|
|
280
|
-
|
|
281
|
-
const producers = getEventProducers(this, eventName);
|
|
312
|
+
clearListeners(eventNames) {
|
|
313
|
+
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
|
|
282
314
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
producers.clear();
|
|
288
|
-
} else {
|
|
289
|
-
anyMap.get(this).clear();
|
|
315
|
+
for (const eventName of eventNames) {
|
|
316
|
+
if (typeof eventName === 'string' || typeof eventName === 'symbol') {
|
|
317
|
+
getListeners(this, eventName).clear();
|
|
290
318
|
|
|
291
|
-
|
|
292
|
-
listeners.clear();
|
|
293
|
-
}
|
|
319
|
+
const producers = getEventProducers(this, eventName);
|
|
294
320
|
|
|
295
|
-
for (const producers of producersMap.get(this).values()) {
|
|
296
321
|
for (const producer of producers) {
|
|
297
322
|
producer.finish();
|
|
298
323
|
}
|
|
299
324
|
|
|
300
325
|
producers.clear();
|
|
326
|
+
} else {
|
|
327
|
+
anyMap.get(this).clear();
|
|
328
|
+
|
|
329
|
+
for (const listeners of eventsMap.get(this).values()) {
|
|
330
|
+
listeners.clear();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
for (const producers of producersMap.get(this).values()) {
|
|
334
|
+
for (const producer of producers) {
|
|
335
|
+
producer.finish();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
producers.clear();
|
|
339
|
+
}
|
|
301
340
|
}
|
|
302
341
|
}
|
|
303
342
|
}
|
|
304
343
|
|
|
305
|
-
listenerCount(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
getEventProducers(this, eventName).size + getEventProducers(this).size;
|
|
309
|
-
}
|
|
344
|
+
listenerCount(eventNames) {
|
|
345
|
+
eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
|
|
346
|
+
let count = 0;
|
|
310
347
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
348
|
+
for (const eventName of eventNames) {
|
|
349
|
+
if (typeof eventName === 'string') {
|
|
350
|
+
count += anyMap.get(this).size + getListeners(this, eventName).size +
|
|
351
|
+
getEventProducers(this, eventName).size + getEventProducers(this).size;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
314
354
|
|
|
315
|
-
|
|
355
|
+
if (typeof eventName !== 'undefined') {
|
|
356
|
+
assertEventName(eventName);
|
|
357
|
+
}
|
|
316
358
|
|
|
317
|
-
|
|
318
|
-
count += value.size;
|
|
319
|
-
}
|
|
359
|
+
count += anyMap.get(this).size;
|
|
320
360
|
|
|
321
|
-
|
|
322
|
-
|
|
361
|
+
for (const value of eventsMap.get(this).values()) {
|
|
362
|
+
count += value.size;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
for (const value of producersMap.get(this).values()) {
|
|
366
|
+
count += value.size;
|
|
367
|
+
}
|
|
323
368
|
}
|
|
324
369
|
|
|
325
370
|
return count;
|
|
@@ -354,4 +399,17 @@ Object.defineProperty(Emittery.Typed, 'Typed', {
|
|
|
354
399
|
value: undefined
|
|
355
400
|
});
|
|
356
401
|
|
|
402
|
+
Object.defineProperty(Emittery, 'listenerAdded', {
|
|
403
|
+
value: listenerAdded,
|
|
404
|
+
writable: false,
|
|
405
|
+
enumerable: true,
|
|
406
|
+
configurable: false
|
|
407
|
+
});
|
|
408
|
+
Object.defineProperty(Emittery, 'listenerRemoved', {
|
|
409
|
+
value: listenerRemoved,
|
|
410
|
+
writable: false,
|
|
411
|
+
enumerable: true,
|
|
412
|
+
configurable: false
|
|
413
|
+
});
|
|
414
|
+
|
|
357
415
|
module.exports = Emittery;
|
package/license
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
|
3
|
+
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "emittery",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Simple and modern async event emitter",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "sindresorhus/emittery",
|
|
7
|
+
"funding": "https://github.com/sindresorhus/emittery?sponsor=1",
|
|
7
8
|
"author": {
|
|
8
9
|
"name": "Sindre Sorhus",
|
|
9
10
|
"email": "sindresorhus@gmail.com",
|
|
10
|
-
"url": "sindresorhus.com"
|
|
11
|
+
"url": "https://sindresorhus.com"
|
|
11
12
|
},
|
|
12
13
|
"engines": {
|
|
13
|
-
"node": ">=
|
|
14
|
+
"node": ">=10"
|
|
14
15
|
},
|
|
15
16
|
"scripts": {
|
|
16
17
|
"test": "xo && nyc ava && tsd"
|
|
@@ -47,13 +48,14 @@
|
|
|
47
48
|
"typed"
|
|
48
49
|
],
|
|
49
50
|
"devDependencies": {
|
|
50
|
-
"@types/node": "^
|
|
51
|
+
"@types/node": "^13.7.5",
|
|
51
52
|
"ava": "^2.4.0",
|
|
52
53
|
"codecov": "^3.1.0",
|
|
53
54
|
"delay": "^4.3.0",
|
|
54
|
-
"nyc": "^
|
|
55
|
-
"
|
|
56
|
-
"
|
|
55
|
+
"nyc": "^15.0.0",
|
|
56
|
+
"p-event": "^4.1.0",
|
|
57
|
+
"tsd": "^0.11.0",
|
|
58
|
+
"xo": "^0.25.4"
|
|
57
59
|
},
|
|
58
60
|
"nyc": {
|
|
59
61
|
"reporter": [
|
package/readme.md
CHANGED
|
@@ -2,22 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
> Simple and modern async event emitter
|
|
4
4
|
|
|
5
|
-
[](https://travis-ci.org/sindresorhus/emittery) [](https://codecov.io/gh/sindresorhus/emittery)
|
|
6
|
-
|
|
7
|
-
It's only ~200 bytes minified and gzipped. [I'm not fanatic about keeping the size at this level though.](https://github.com/sindresorhus/emittery/pull/5#issuecomment-347479211)
|
|
5
|
+
[](https://travis-ci.org/sindresorhus/emittery) [](https://codecov.io/gh/sindresorhus/emittery) [](https://bundlephobia.com/result?p=emittery)
|
|
8
6
|
|
|
9
7
|
It works in Node.js and the browser (using a bundler).
|
|
10
8
|
|
|
11
9
|
Emitting events asynchronously is important for production code where you want the least amount of synchronous operations. Since JavaScript is single-threaded, no other code can run while doing synchronous operations. For Node.js, that means it will block other requests, defeating the strength of the platform, which is scalability through async. In the browser, a synchronous operation could potentially cause lags and block user interaction.
|
|
12
10
|
|
|
13
|
-
|
|
14
11
|
## Install
|
|
15
12
|
|
|
16
13
|
```
|
|
17
14
|
$ npm install emittery
|
|
18
15
|
```
|
|
19
16
|
|
|
20
|
-
|
|
21
17
|
## Usage
|
|
22
18
|
|
|
23
19
|
```js
|
|
@@ -27,36 +23,114 @@ const emitter = new Emittery();
|
|
|
27
23
|
|
|
28
24
|
emitter.on('🦄', data => {
|
|
29
25
|
console.log(data);
|
|
30
|
-
// '🌈'
|
|
31
26
|
});
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
const myUnicorn = Symbol('🦄');
|
|
29
|
+
|
|
30
|
+
emitter.on(myUnicorn, data => {
|
|
31
|
+
console.log(`Unicorns love ${data}`);
|
|
32
|
+
});
|
|
35
33
|
|
|
34
|
+
emitter.emit('🦄', '🌈'); // Will trigger printing '🌈'
|
|
35
|
+
emitter.emit(myUnicorn, '🦋'); // Will trigger printing 'Unicorns love 🦋'
|
|
36
|
+
```
|
|
36
37
|
|
|
37
38
|
## API
|
|
38
39
|
|
|
40
|
+
### eventName
|
|
41
|
+
|
|
42
|
+
Emittery accepts strings and symbols as event names.
|
|
43
|
+
|
|
44
|
+
Symbol event names can be used to avoid name collisions when your classes are extended, especially for internal events.
|
|
45
|
+
|
|
39
46
|
### emitter = new Emittery()
|
|
40
47
|
|
|
41
|
-
#### on(eventName, listener)
|
|
48
|
+
#### on(eventName | eventName[], listener)
|
|
42
49
|
|
|
43
|
-
Subscribe to
|
|
50
|
+
Subscribe to one or more events.
|
|
44
51
|
|
|
45
52
|
Returns an unsubscribe method.
|
|
46
53
|
|
|
47
54
|
Using the same listener multiple times for the same event will result in only one method call per emitted event.
|
|
48
55
|
|
|
56
|
+
```js
|
|
57
|
+
const Emittery = require('emittery');
|
|
58
|
+
|
|
59
|
+
const emitter = new Emittery();
|
|
60
|
+
|
|
61
|
+
emitter.on('🦄', data => {
|
|
62
|
+
console.log(data);
|
|
63
|
+
});
|
|
64
|
+
emitter.on(['🦄', '🐶'], data => {
|
|
65
|
+
console.log(data);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
emitter.emit('🦄', '🌈'); // log => '🌈' x2
|
|
69
|
+
emitter.emit('🐶', '🍖'); // log => '🍖'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
##### Custom subscribable events
|
|
73
|
+
|
|
74
|
+
Emittery exports some symbols which represent custom events that can be passed to `Emitter.on` and similar methods.
|
|
75
|
+
|
|
76
|
+
- `Emittery.listenerAdded` - Fires when an event listener was added.
|
|
77
|
+
- `Emittery.listenerRemoved` - Fires when an event listener was removed.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
const Emittery = require('emittery');
|
|
81
|
+
|
|
82
|
+
const emitter = new Emittery();
|
|
83
|
+
|
|
84
|
+
emitter.on(Emittery.listenerAdded, ({listener, eventName}) => {
|
|
85
|
+
console.log(listener);
|
|
86
|
+
//=> data => {}
|
|
87
|
+
|
|
88
|
+
console.log(eventName);
|
|
89
|
+
//=> '🦄'
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
emitter.on('🦄', data => {
|
|
93
|
+
// Handle data
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
###### Listener data
|
|
98
|
+
|
|
99
|
+
- `listener` - The listener that was added.
|
|
100
|
+
- `eventName` - The name of the event that was added or removed if `.on()` or `.off()` was used, or `undefined` if `.onAny()` or `.offAny()` was used.
|
|
101
|
+
|
|
102
|
+
Only events that are not of this type are able to trigger these events.
|
|
103
|
+
|
|
49
104
|
##### listener(data)
|
|
50
105
|
|
|
51
|
-
#### off(eventName, listener)
|
|
106
|
+
#### off(eventName | eventName[], listener)
|
|
107
|
+
|
|
108
|
+
Remove one or more event subscriptions.
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
const Emittery = require('emittery');
|
|
112
|
+
|
|
113
|
+
const emitter = new Emittery();
|
|
52
114
|
|
|
53
|
-
|
|
115
|
+
const listener = data => console.log(data);
|
|
116
|
+
(async () => {
|
|
117
|
+
emitter.on(['🦄', '🐶', '🦊'], listener);
|
|
118
|
+
await emitter.emit('🦄', 'a');
|
|
119
|
+
await emitter.emit('🐶', 'b');
|
|
120
|
+
await emitter.emit('🦊', 'c');
|
|
121
|
+
emitter.off('🦄', listener);
|
|
122
|
+
emitter.off(['🐶', '🦊'], listener);
|
|
123
|
+
await emitter.emit('🦄', 'a'); // nothing happens
|
|
124
|
+
await emitter.emit('🐶', 'b'); // nothing happens
|
|
125
|
+
await emitter.emit('🦊', 'c'); // nothing happens
|
|
126
|
+
})();
|
|
127
|
+
```
|
|
54
128
|
|
|
55
129
|
##### listener(data)
|
|
56
130
|
|
|
57
|
-
#### once(eventName)
|
|
131
|
+
#### once(eventName | eventName[])
|
|
58
132
|
|
|
59
|
-
Subscribe to
|
|
133
|
+
Subscribe to one or more events only once. It will be unsubscribed after the first event.
|
|
60
134
|
|
|
61
135
|
Returns a promise for the event data when `eventName` is emitted.
|
|
62
136
|
|
|
@@ -69,8 +143,12 @@ emitter.once('🦄').then(data => {
|
|
|
69
143
|
console.log(data);
|
|
70
144
|
//=> '🌈'
|
|
71
145
|
});
|
|
146
|
+
emitter.once(['🦄', '🐶']).then(data => {
|
|
147
|
+
console.log(data);
|
|
148
|
+
});
|
|
72
149
|
|
|
73
|
-
emitter.emit('🦄', '🌈');
|
|
150
|
+
emitter.emit('🦄', '🌈'); // log => '🌈' x2
|
|
151
|
+
emitter.emit('🐶', '🍖'); // nothing happens
|
|
74
152
|
```
|
|
75
153
|
|
|
76
154
|
#### events(eventName)
|
|
@@ -125,6 +203,35 @@ for await (const data of iterator) {
|
|
|
125
203
|
}
|
|
126
204
|
```
|
|
127
205
|
|
|
206
|
+
It accepts multiple event names.
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
const Emittery = require('emittery');
|
|
210
|
+
|
|
211
|
+
const emitter = new Emittery();
|
|
212
|
+
const iterator = emitter.events(['🦄', '🦊']);
|
|
213
|
+
|
|
214
|
+
emitter.emit('🦄', '🌈1'); // Buffered
|
|
215
|
+
emitter.emit('🦊', '🌈2'); // Buffered
|
|
216
|
+
|
|
217
|
+
iterator
|
|
218
|
+
.next()
|
|
219
|
+
.then(({value, done}) => {
|
|
220
|
+
// done === false
|
|
221
|
+
// value === '🌈1'
|
|
222
|
+
return iterator.next();
|
|
223
|
+
})
|
|
224
|
+
.then(({value, done}) => {
|
|
225
|
+
// done === false
|
|
226
|
+
// value === '🌈2'
|
|
227
|
+
// Revoke subscription
|
|
228
|
+
return iterator.return();
|
|
229
|
+
})
|
|
230
|
+
.then(({done}) => {
|
|
231
|
+
// done === true
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
128
235
|
#### emit(eventName, data?)
|
|
129
236
|
|
|
130
237
|
Trigger an event asynchronously, optionally with some data. Listeners are called in the order they were added, but executed concurrently.
|
|
@@ -183,15 +290,15 @@ iterator.next()
|
|
|
183
290
|
|
|
184
291
|
In the same way as for `events`, you can subscribe by using the `for await` statement
|
|
185
292
|
|
|
186
|
-
#### clearListeners()
|
|
293
|
+
#### clearListeners(eventNames?)
|
|
187
294
|
|
|
188
295
|
Clear all event listeners on the instance.
|
|
189
296
|
|
|
190
|
-
If `
|
|
297
|
+
If `eventNames` is given, only the listeners for that events are cleared.
|
|
191
298
|
|
|
192
|
-
#### listenerCount(
|
|
299
|
+
#### listenerCount(eventNames?)
|
|
193
300
|
|
|
194
|
-
The number of listeners for the `
|
|
301
|
+
The number of listeners for the `eventNames` or all events if not specified.
|
|
195
302
|
|
|
196
303
|
#### bindMethods(target, methodNames?)
|
|
197
304
|
|
|
@@ -207,7 +314,6 @@ new Emittery().bindMethods(object);
|
|
|
207
314
|
object.emit('event');
|
|
208
315
|
```
|
|
209
316
|
|
|
210
|
-
|
|
211
317
|
## TypeScript
|
|
212
318
|
|
|
213
319
|
The default `Emittery` class does not let you type allowed event names and their associated data. However, you can use `Emittery.Typed` with generics:
|
|
@@ -238,14 +344,12 @@ const instance = new MyClass();
|
|
|
238
344
|
instance.emit('event');
|
|
239
345
|
```
|
|
240
346
|
|
|
241
|
-
|
|
242
347
|
## Scheduling details
|
|
243
348
|
|
|
244
349
|
Listeners are not invoked for events emitted *before* the listener was added. Removing a listener will prevent that listener from being invoked, even if events are in the process of being (asynchronously!) emitted. This also applies to `.clearListeners()`, which removes all listeners. Listeners will be called in the order they were added. So-called *any* listeners are called *after* event-specific listeners.
|
|
245
350
|
|
|
246
351
|
Note that when using `.emitSerial()`, a slow listener will delay invocation of subsequent listeners. It's possible for newer events to overtake older ones.
|
|
247
352
|
|
|
248
|
-
|
|
249
353
|
## FAQ
|
|
250
354
|
|
|
251
355
|
### How is this different than the built-in `EventEmitter` in Node.js?
|
|
@@ -285,7 +389,6 @@ emitter.on('🦄', ([foo, bar]) => {
|
|
|
285
389
|
emitter.emit('🦄', [foo, bar]);
|
|
286
390
|
```
|
|
287
391
|
|
|
288
|
-
|
|
289
392
|
## Related
|
|
290
393
|
|
|
291
394
|
- [p-event](https://github.com/sindresorhus/p-event) - Promisify an event by waiting for it to be emitted
|