@creejs/commons-events 1.0.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/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # commons-events
2
+ An EventEmitter whose API like NodeJS, but more "Common"
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict'
2
+ module.exports = require('./lib')
@@ -0,0 +1,6 @@
1
+ 'use strict'
2
+
3
+ const DefaultOwner = 'DOwner$#$'
4
+ module.exports = {
5
+ DefaultOwner
6
+ }
@@ -0,0 +1,371 @@
1
+ 'use strict'
2
+ // 3rd
3
+ // internal
4
+ const {
5
+ TypeUtils: { isNil },
6
+ TypeAssert: {
7
+ assertString, assertFunction, assertNumber,
8
+ assertStringOrSymbol, assertNotNil
9
+ }
10
+ } = require('@creejs/commons-lang')
11
+
12
+ // owned
13
+ const Event = require('./event')
14
+ // eslint-disable-next-line no-unused-vars
15
+ const Listener = require('./listener')
16
+
17
+ // module vars
18
+ const MixinMethods = [
19
+ 'on', 'once', 'addListener', 'prependListener', 'prependOnceListener',
20
+ 'off', 'offAll', 'offOwner', 'removeAllListeners', 'removeListener',
21
+ 'emit', 'setMaxListeners', 'getMaxListeners', 'hasOwner',
22
+ 'listeners', 'listenerCount', 'eventNames', 'rawListeners'
23
+ ]
24
+ let DefaultMaxListeners = 10
25
+ /**
26
+ * 1. An EventEmitter follows the API of NodeJS EventEmitter.
27
+ * 2. Enhancement:
28
+ * * Listeners are grouped by "owner".
29
+ * * Operation via "owner"
30
+ * * Duplicate listeners are filtered out. Only unique One kept.
31
+ */
32
+ class EventEmitter {
33
+ /**
34
+ * Mixes EventEmitter methods into the given object.
35
+ * @template T
36
+ * @param {T} obj - The target object to mix methods into.
37
+ * @returns {T} The modified object with EventEmitter methods.
38
+ */
39
+ static mixin (obj) {
40
+ const emitter = new EventEmitter()
41
+ // @ts-ignore
42
+ obj.__emitter = emitter
43
+ for (const name of MixinMethods) {
44
+ // @ts-ignore
45
+ const method = emitter[name]
46
+ // @ts-ignore
47
+ obj[name] = method.bind(emitter)
48
+ }
49
+ return obj
50
+ }
51
+
52
+ static get defaultMaxListeners () {
53
+ return DefaultMaxListeners
54
+ }
55
+
56
+ static set defaultMaxListeners (maxListeners) {
57
+ assertNumber(maxListeners)
58
+ DefaultMaxListeners = maxListeners ?? 10
59
+ }
60
+
61
+ /**
62
+ */
63
+ constructor () {
64
+ /**
65
+ * @type {Map<string|Symbol, Event>}
66
+ */
67
+ this._name2Event = new Map()
68
+ this._maxListeners = DefaultMaxListeners
69
+ }
70
+
71
+ /**
72
+ * Alias of {@linkcode EventEmitter.addListener}
73
+ * @param {string|Symbol} eventName
74
+ * @param {function} listener
75
+ * @param {*} [owner]
76
+ * @returns {this}
77
+ */
78
+ addListener (eventName, listener, owner) {
79
+ return this.on(eventName, listener, owner)
80
+ }
81
+
82
+ /**
83
+ * Adds the listener function to the beginning of the listeners array for the event named eventName.
84
+ * @param {string|Symbol} eventName
85
+ * @param {function} listener
86
+ * @param {*} [owner]
87
+ * @returns {this}
88
+ */
89
+ prependListener (eventName, listener, owner) {
90
+ assertString(eventName)
91
+ assertFunction(listener)
92
+ this._checkMaxListeners(eventName)
93
+
94
+ const event = this._getOrCreateEvent(eventName)
95
+ event.prependListener(listener, owner)
96
+ return this
97
+ }
98
+
99
+ /**
100
+ * Adds a one-time listener function for the event named eventName to the beginning of the listeners array.
101
+ * @param {string|Symbol} eventName
102
+ * @param {function} listener
103
+ * @param {*} [owner]
104
+ * @returns {this}
105
+ */
106
+ prependOnceListener (eventName, listener, owner) {
107
+ assertString(eventName)
108
+ assertFunction(listener)
109
+ this._checkMaxListeners(eventName)
110
+
111
+ const event = this._getOrCreateEvent(eventName)
112
+ event.prependOnceListener(listener, owner)
113
+ return this
114
+ }
115
+
116
+ /**
117
+ * Synchronously calls each of the listeners registered for the event named eventName,
118
+ * in the order they were registered, passing the supplied arguments to each.
119
+ * Returns true if the event had listeners, false otherwise.
120
+ * @param {string|Symbol} eventName - The name of the event.
121
+ * @param {...*} args arguments to pass to the listeners.
122
+ * @returns {boolean}
123
+ */
124
+ emit (eventName, ...args) {
125
+ const event = this._name2Event.get(eventName)
126
+ if (event == null || event.isEmpty()) {
127
+ return false
128
+ }
129
+ event.emit(...args)
130
+ return true
131
+ }
132
+
133
+ /**
134
+ * Returns an array listing the events for which the emitter has registered listeners.
135
+ * @returns {Array<string|Symbol>} An array of event names.
136
+ */
137
+ eventNames () {
138
+ return [...this._name2Event.keys()]
139
+ }
140
+
141
+ getMaxListeners () {
142
+ return this._maxListeners
143
+ }
144
+
145
+ /**
146
+ * Returns the number of listeners listening for the event named eventName.
147
+ * If listener is provided, it will return how many times the listener is found
148
+ * in the list of the listeners of the event.
149
+ * @param {string|Symbol} eventName - The name of the event to check
150
+ * @param {function} [listener] - Optional specific listener to count
151
+ * @returns {number} The number of listeners for the event
152
+ */
153
+ listenerCount (eventName, listener) {
154
+ assertStringOrSymbol(eventName, 'eventName')
155
+ const event = this._name2Event.get(eventName)
156
+ if (event == null || event.isEmpty()) {
157
+ return 0
158
+ }
159
+ return event.listenerCount(listener)
160
+ }
161
+
162
+ /**
163
+ * Returns a copy of the array of listeners for the event named eventName.
164
+ * 1. if a callback function added multiple times, it will be returned multiple times.
165
+ * @param {string} eventName
166
+ * @returns {Array<function>}
167
+ */
168
+ listeners (eventName) {
169
+ assertStringOrSymbol(eventName, 'eventName')
170
+ const event = this._name2Event.get(eventName)
171
+ if (event == null || event.isEmpty()) {
172
+ return []
173
+ }
174
+ return event.callbacks()
175
+ }
176
+
177
+ /**
178
+ * Removes a callback function from a specific event.
179
+ * 1. If the event doesn't exist or has no listeners, do nothing
180
+ * 2. if one callback function added multiple times, all of them will be removed.
181
+ * * !!! This is different from the behavior of Node.js EventEmitter.
182
+ *
183
+ * @param {string} eventName - The name of the event to remove callback from
184
+ * @param {function} callback - The callback function to remove
185
+ * @returns {EventEmitter} Returns the emitter instance for chaining
186
+ */
187
+ off (eventName, callback) {
188
+ const event = this._name2Event.get(eventName)
189
+ if (event == null) {
190
+ return this
191
+ }
192
+ event.removeListener(callback)
193
+ if (event.isEmpty()) {
194
+ this._name2Event.delete(eventName)
195
+ return this
196
+ }
197
+ return this
198
+ }
199
+
200
+ /**
201
+ * Removes all listeners for the specified event.
202
+ * if owner is limited, only the listeners of the owner will be removed.
203
+ *
204
+ * @param {string|Symbol} eventName - The name of the event to clear listeners for.
205
+ * @param {*} owner - The owner whose listeners should be removed.
206
+ * @returns {EventEmitter} The emitter instance for chaining.
207
+ */
208
+ offAll (eventName, owner) {
209
+ assertStringOrSymbol(eventName, 'eventName')
210
+ const event = this._name2Event.get(eventName)
211
+ if (event == null) {
212
+ return this
213
+ }
214
+ event.removeAllListeners(owner)
215
+ if (event.isEmpty()) {
216
+ this._name2Event.delete(eventName)
217
+ return this
218
+ }
219
+ return this
220
+ }
221
+
222
+ /**
223
+ * Removes all event listeners belonging to the specified owner.
224
+ * @param {*} owner - The owner whose listeners should be removed.
225
+ * @returns {this}
226
+ */
227
+ offOwner (owner) {
228
+ assertNotNil(owner, 'owner')
229
+ const events = [...this._name2Event.values()]
230
+ for (const event of events) {
231
+ event.removeAllListeners(owner)
232
+ if (event.isEmpty()) {
233
+ this._name2Event.delete(event.name)
234
+ }
235
+ }
236
+ return this
237
+ }
238
+
239
+ /**
240
+ * Adds the listener function to the end of the listeners array for the event named eventName.
241
+ * @param {string|Symbol} eventName
242
+ * @param {function} callback
243
+ * @param {*} [owner]
244
+ * @returns {this}
245
+ */
246
+ on (eventName, callback, owner) {
247
+ assertString(eventName)
248
+ assertFunction(callback)
249
+ this._checkMaxListeners(eventName)
250
+
251
+ const event = this._getOrCreateEvent(eventName)
252
+ event.addListener(callback, owner)
253
+ return this
254
+ }
255
+
256
+ /**
257
+ * Checks if the number of listeners for the given event exceeds the maximum allowed.
258
+ * Emits a warning if the listener count reaches or exceeds the maxListeners threshold.
259
+ * @private
260
+ * @param {string|Symbol} eventName - The name of the event to check
261
+ * @returns {void}
262
+ */
263
+ _checkMaxListeners (eventName) {
264
+ let listenerCount = 0
265
+ if (this._maxListeners !== 0 && this._maxListeners !== Infinity && (listenerCount = this.listenerCount(eventName)) >= this._maxListeners) {
266
+ console.warn(`maxlistenersexceededwarning: Possible EventEmitter memory leak detected. ${listenerCount} ${eventName} listeners added to [${this}]. Use emitter.setMaxListeners() to increase limit`)
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Adds a listener that will be invoked only once for the event named eventName.SIGINT listeners added to [process]. Use emitter.setMaxListeners() to increase limit`)
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Adds a one-time callback function for the specified event.
277
+ * The callback is invoked only once and then automatically removed.
278
+ *
279
+ * @param {string} eventName - The name of the event to listen for.
280
+ * @param {Function} callback - The callback function to execute when the event is emitted.
281
+ * @param {*} [owner] - Optional owner used to group listener callbacks.
282
+ * @returns {EventEmitter} Returns the emitter instance for chaining.
283
+ */
284
+ once (eventName, callback, owner) {
285
+ assertString(eventName)
286
+ assertFunction(callback)
287
+
288
+ const event = this._getOrCreateEvent(eventName)
289
+ event.addOnceListener(callback, owner)
290
+ return this
291
+ }
292
+
293
+ /**
294
+ * Returns a copy of the array of listeners for the event named eventName,
295
+ * including any wrappers (such as those created by .once()).
296
+ * @param {string} eventName - The name of the event.
297
+ * @returns {Listener[]} An array of raw listener functions, or empty array if none exist.
298
+ */
299
+ rawListeners (eventName) {
300
+ return this._name2Event.get(eventName)?.rawListeners() || []
301
+ }
302
+
303
+ /**
304
+ * Alias of {@linkcode EventEmitter.offAll}
305
+ * @param {string|Symbol} eventName - The name of the event to remove listeners for.
306
+ * @param {*} owner - The owner of the listeners to be removed.
307
+ * @returns {EventEmitter} The emitter instance for chaining.
308
+ */
309
+ removeAllListeners (eventName, owner) {
310
+ return this.offAll(eventName, owner)
311
+ }
312
+
313
+ /**
314
+ * Alias of {@linkcode EventEmitter.off}
315
+ * @param {string} eventName - The name of the event.
316
+ * @param {Function} callback - The callback function to remove.
317
+ * @returns {EventEmitter} The emitter instance for chaining.
318
+ */
319
+ removeListener (eventName, callback) {
320
+ return this.off(eventName, callback)
321
+ }
322
+
323
+ /**
324
+ * Sets the maximum number of listeners that can be added to this event emitter.
325
+ * @param {number} max - The maximum number of listeners.
326
+ * @returns {this} The set maximum number of listeners.
327
+ */
328
+ setMaxListeners (max) {
329
+ assertNumber(max)
330
+ if (max < 0) {
331
+ throw new RangeError('maxListeners must >=0')
332
+ }
333
+ this._maxListeners = max
334
+ return this
335
+ }
336
+
337
+ /**
338
+ * Gets an existing event by name or creates a new one if it doesn't exist.
339
+ * @private
340
+ * @param {string|Symbol} eventName - The name of the event to get or create.
341
+ * @returns {Event} The existing or newly created Event instance.
342
+ */
343
+ _getOrCreateEvent (eventName) {
344
+ if (this._name2Event.has(eventName)) {
345
+ // @ts-ignore
346
+ return this._name2Event.get(eventName)
347
+ }
348
+ const event = new Event(eventName)
349
+ this._name2Event.set(eventName, event)
350
+ return event
351
+ }
352
+
353
+ /**
354
+ * Checks if the specified owner has any registered events.
355
+ * @param {Object} owner - The owner object to check for registered events.
356
+ * @returns {boolean} True if the owner has any registered events, false otherwise.
357
+ */
358
+ hasOwner (owner) {
359
+ if (isNil(owner)) {
360
+ return false
361
+ }
362
+ for (const event of this._name2Event.values()) {
363
+ if (event.hasOwner(owner)) {
364
+ return true
365
+ }
366
+ }
367
+ return false
368
+ }
369
+ }
370
+
371
+ module.exports = EventEmitter
package/lib/event.js ADDED
@@ -0,0 +1,358 @@
1
+ 'use strict'
2
+ // 3rd
3
+ // internal
4
+ const { TypeUtils: { isFunction, isNil }, TypeAssert: { assertStringOrSymbol } } = require('@creejs/commons-lang')
5
+ // eslint-disable-next-line no-unused-vars
6
+ const Listener = require('./listener')
7
+ const { DefaultOwner } = require('./constants')
8
+
9
+ // module vars
10
+
11
+ /**
12
+ * An Event definition
13
+ * 1. listeners are grouped by owner
14
+ * * if not specified, group it into one Default owner
15
+ * 2. one listener may belong to multiple owners
16
+ * 3. one owner may have multiple listeners
17
+ */
18
+ class Event {
19
+ static get DefaultOwner () {
20
+ return DefaultOwner
21
+ }
22
+
23
+ /**
24
+ * Creates a new Event instance with the specified name.
25
+ * @param {string|Symbol} eventName - The name of the event.
26
+ */
27
+ constructor (eventName) {
28
+ assertStringOrSymbol(eventName, 'eventName')
29
+ this._name = eventName
30
+ /**
31
+ * use Set to store unique listeners
32
+ * @type {Set<function>}
33
+ */
34
+ this._callbacks = new Set()
35
+ /**
36
+ * @type {Array<Listener>}
37
+ */
38
+ this._listeners = [] // user array to keep order
39
+ /**
40
+ * @type {Map<function, Set<Listener>>}
41
+ */
42
+ this._callback2Listeners = new Map()
43
+ /**
44
+ * use listener to index owners. One Listener Instance only belongs to one owner
45
+ * @type {Map<Listener, *>}
46
+ */
47
+ this._listener2Owner = new Map()
48
+ /**
49
+ * use "owner" to group listeners; one owner may have multiple listeners
50
+ * @type {Map<*, Set<Listener>>}
51
+ */
52
+ this._owner2Listeners = new Map()
53
+ }
54
+
55
+ /**
56
+ * Name of the event.
57
+ * @returns {string|Symbol}
58
+ */
59
+ get name () {
60
+ return this._name
61
+ }
62
+
63
+ isEmpty () {
64
+ return this._callbacks.size === 0
65
+ }
66
+
67
+ /**
68
+ * Returns a copy of the raw listeners array.
69
+ * @returns {Array<Listener>} A shallow copy of the listeners array.
70
+ */
71
+ rawListeners () {
72
+ return [...this._listeners]
73
+ }
74
+
75
+ /**
76
+ * Returns the number of listeners listening for the event.
77
+ * If callback is provided, it will return how many times the callback is found in the list of the listeners
78
+ * @param {Function} [callback] - The callback function to count
79
+ * @returns {number}
80
+ */
81
+ listenerCount (callback) {
82
+ if (callback == null) {
83
+ return this._listeners.length
84
+ }
85
+ return this._callback2Listeners.get(callback)?.size ?? 0
86
+ }
87
+
88
+ /**
89
+ * Returns a shallow copy of the registered callbacks array.
90
+ * if one callback function is added multiple times, it will be returned multiple times.
91
+ * @returns {Array<function>} A new array containing all registered callbacks.
92
+ */
93
+ callbacks () {
94
+ return [...this.rawListeners().map(listener => listener.callback)]
95
+ }
96
+
97
+ /**
98
+ * Emits current event, invoking all registered listeners by order.
99
+ * @param {...*} args - Arguments to be passed to each listener.
100
+ * @returns {boolean} True if the event had listeners, false otherwise.
101
+ */
102
+ emit (...args) {
103
+ if (this._listeners.length === 0) {
104
+ return false
105
+ }
106
+ // Clone _listeners, it may be changed during call listener
107
+ for (const listener of [...this._listeners]) {
108
+ listener.invoke(...args)
109
+ }
110
+ return true
111
+ }
112
+
113
+ /**
114
+ * Checks if listener is registered with this event.
115
+ * @param {function} callback - The listener function to check.
116
+ * @returns {boolean} True if the listener is registered, false otherwise.
117
+ */
118
+ hasListener (callback) {
119
+ if (!isFunction(callback)) {
120
+ return false
121
+ }
122
+ return this._callbacks.has(callback)
123
+ }
124
+
125
+ /**
126
+ * Checks if owner has any registered listeners.
127
+ * @param {*} owner - The owner to check for registered listeners.
128
+ * @returns {boolean} True if the owner has listeners, false otherwise.
129
+ */
130
+ hasOwner (owner) {
131
+ if (isNil(owner)) {
132
+ return false
133
+ }
134
+ return this._owner2Listeners.has(owner)
135
+ }
136
+
137
+ /**
138
+ * Adds an event listener
139
+ * @param {function} callback - The callback function to be invoked when the event occurs.
140
+ * @param {*} [owner] - use "owner" to group listeners, then can remove all listeners for an owner.
141
+ * @returns {boolean} true if added, false otherwise.
142
+ */
143
+ addListener (callback, owner) {
144
+ return this._addListener(callback, owner, false, false)
145
+ }
146
+
147
+ /**
148
+ * Prepends a listener callback to the beginning of the listeners array.
149
+ * No checks are made to see if the listener has already been added.
150
+ * Multiple calls passing the same combination of eventName and listener will result in the listener being added,
151
+ * and called, multiple times.
152
+ * @param {Function} callback - The callback function to be executed when the event is emitted.
153
+ * @param {Object} owner - The owner object to which the listener belongs.
154
+ * @returns {boolean}
155
+ */
156
+ prependListener (callback, owner) {
157
+ return this._addListener(callback, owner, false, true)
158
+ }
159
+
160
+ /**
161
+ * Adds a one-time listener for the event. The listener is automatically removed after being invoked once.
162
+ * @param {Function} callback - The function to call when the event is triggered.
163
+ * @param {Object} [owner] - The object that owns the callback (used for binding `this` context).
164
+ * @returns {boolean}
165
+ */
166
+ addOnceListener (callback, owner) {
167
+ return this._addListener(callback, owner, true, false)
168
+ }
169
+
170
+ /**
171
+ * Adds a one-time event listener that will be automatically removed after being triggered once.
172
+ * The listener will only be triggered if the event is pretended (simulated).
173
+ * @param {Function} callback - The function to call when the event is triggered.
174
+ * @param {Object} owner - The object that owns the listener (used for reference tracking).
175
+ * @returns {boolean} The listener function that was added.
176
+ */
177
+ prependOnceListener (callback, owner) {
178
+ return this._addListener(callback, owner, true, true)
179
+ }
180
+
181
+ /**
182
+ * Adds a listener for the event.
183
+ * @param {Function} callback - The callback function to be executed when the event occurs
184
+ * @param {*} [owner] - The owner object that the listener belongs to (defaults to DeaultOwner)
185
+ * @param {boolean} [isOnce=false] - Whether the listener should be removed after first invocation
186
+ * @param {boolean} [isPrepend=false] - Whether the listener should be inert at the beginning of the listeners array
187
+ * @returns {boolean} Returns true if listener was added successfully, false if callback is nil or duplicate
188
+ * @protected
189
+ */
190
+ _addListener (callback, owner, isOnce, isPrepend) {
191
+ if (isNil(callback)) {
192
+ return false
193
+ }
194
+ if (!this._callbacks.has(callback)) {
195
+ this._callbacks.add(callback)
196
+ }
197
+ // a listener must belong to an owner
198
+ owner = owner ?? DefaultOwner
199
+
200
+ // use Listener to wrap callback
201
+ const listener = new Listener(this, callback, isOnce)
202
+ listener.owner = owner
203
+ if (isPrepend) {
204
+ this._listeners.unshift(listener)
205
+ } else {
206
+ this._listeners.push(listener)
207
+ }
208
+
209
+ // index, rapadly find one listener's owner
210
+ this._listener2Owner.set(listener, owner)
211
+
212
+ // one callback function may be registered many times
213
+ let callbackListeners = this._callback2Listeners.get(callback)
214
+ if (callbackListeners == null) {
215
+ callbackListeners = new Set()
216
+ this._callback2Listeners.set(callback, callbackListeners)
217
+ }
218
+ callbackListeners.add(listener)
219
+
220
+ // group by owner
221
+ let ownerListeners = this._owner2Listeners.get(owner)
222
+ if (ownerListeners == null) {
223
+ ownerListeners = new Set()
224
+ this._owner2Listeners.set(owner, ownerListeners)
225
+ }
226
+ ownerListeners.add(listener)
227
+
228
+ return true
229
+ }
230
+
231
+ /**
232
+ * Removes a callback
233
+ * @param {Function} callback - The callback function to remove.
234
+ * @returns {boolean} true if removed, false otherwise.
235
+ */
236
+ removeListener (callback) {
237
+ if (isNil(callback)) {
238
+ return false
239
+ }
240
+ if (!this._callbacks.has(callback)) {
241
+ return false
242
+ }
243
+ this._callbacks.delete(callback)
244
+
245
+ const listeners = this._callback2Listeners.get(callback)
246
+ if (listeners == null) { // should not happen
247
+ return false
248
+ }
249
+ this._callback2Listeners.delete(callback)
250
+ for (const listener of listeners) {
251
+ // remove from global index
252
+ const index = this._listeners.indexOf(listener)
253
+ index !== -1 && this._listeners.splice(this._listeners.indexOf(listener), 1)
254
+
255
+ // remove from owner index
256
+ const owner = this._listener2Owner.get(listener)
257
+ if (owner == null) {
258
+ continue
259
+ }
260
+ this._listener2Owner.delete(listener)
261
+ const ownerListeners = this._owner2Listeners.get(owner)
262
+ if (ownerListeners == null) {
263
+ continue
264
+ }
265
+ ownerListeners.delete(listener)
266
+ if (ownerListeners.size === 0) {
267
+ this._owner2Listeners.delete(owner)
268
+ }
269
+ }
270
+ return true
271
+ }
272
+
273
+ /**
274
+ * Removes a listener from both global and owner indexes.
275
+ * @param {Listener} listener - The listener function to remove
276
+ */
277
+ _remove (listener) {
278
+ // remove from global index
279
+ const index = this._listeners.indexOf(listener)
280
+ index !== -1 && this._listeners.splice(index, 1)
281
+
282
+ // clean callback index
283
+ const { callback } = listener
284
+ const callbackListeners = this._callback2Listeners.get(callback)
285
+ if (callbackListeners != null) {
286
+ callbackListeners.delete(listener)
287
+ if (callbackListeners.size === 0) {
288
+ this._callback2Listeners.delete(callback)
289
+ this._callbacks.delete(callback)
290
+ }
291
+ }
292
+
293
+ // remove from owner index
294
+ const owner = this._listener2Owner.get(listener)
295
+ if (owner == null) {
296
+ return
297
+ }
298
+ this._listener2Owner.delete(listener)
299
+ const ownerListeners = this._owner2Listeners.get(owner)
300
+ if (ownerListeners == null) {
301
+ return
302
+ }
303
+ ownerListeners.delete(listener)
304
+ if (ownerListeners.size === 0) {
305
+ this._owner2Listeners.delete(owner)
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Removes all event listeners
311
+ * @param {*} [owner] - Without owner, all listeners will be removed; otherwise, only listeners of "owner" will be removed.
312
+ * @returns {this}
313
+ */
314
+ removeAllListeners (owner) {
315
+ // remove listeners of owner
316
+ if (isNil(owner)) {
317
+ // remove all Listeners
318
+ this._callbacks.clear()
319
+ this._listeners.length = 0
320
+ this._callback2Listeners.clear()
321
+ this._listener2Owner.clear()
322
+ this._owner2Listeners.clear()
323
+ return this
324
+ }
325
+ // all listeners of the owner
326
+ const ownerListeners = this._owner2Listeners.get(owner)
327
+ // no owner
328
+ if (ownerListeners == null) {
329
+ return this
330
+ }
331
+ // clear owner index
332
+ this._owner2Listeners.delete(owner)
333
+ // clearn listeners of owner one by one
334
+ for (const listener of ownerListeners) {
335
+ // remove from global index
336
+ const index = this._listeners.indexOf(listener)
337
+ index !== -1 && this._listeners.splice(this._listeners.indexOf(listener), 1)
338
+
339
+ // clean listener-owner index
340
+ this._listener2Owner.delete(listener)
341
+
342
+ // one callback function may be registered many times, has many Listeners
343
+ const { callback } = listener
344
+ const callbackListeners = this._callback2Listeners.get(callback)
345
+ if (callbackListeners == null) {
346
+ continue
347
+ }
348
+ callbackListeners.delete(listener)
349
+ if (callbackListeners.size === 0) {
350
+ this._callback2Listeners.delete(callback)
351
+ this._callbacks.delete(callback)
352
+ }
353
+ }
354
+ return this
355
+ }
356
+ }
357
+
358
+ module.exports = Event
package/lib/index.js ADDED
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const EventEmitter = require('./event-emitter')
4
+
5
+ module.exports = {
6
+ EventEmitter
7
+ }
@@ -0,0 +1,93 @@
1
+ 'use strict'
2
+ // 3rd
3
+ // internal
4
+ const { TypeAssert: { assertFunction, assertNotNil } } = require('@creejs/commons-lang')
5
+
6
+ // owned
7
+ const { DefaultOwner } = require('./constants')
8
+ // eslint-disable-next-line no-unused-vars
9
+ const Event = require('./event')
10
+ /**
11
+ * Wraps a function to be called when an event is fired.
12
+ * @class Listener
13
+ */
14
+ class Listener {
15
+ /**
16
+ * @param {Event} event
17
+ * @param {function} callback - The function to be called when event is fired
18
+ * @param {boolean} [isOnce=false] - is a one time listener?
19
+ */
20
+ constructor (event, callback, isOnce = false) {
21
+ assertNotNil(event, 'event')
22
+ assertFunction(callback, 'callback')
23
+ this._event = event
24
+ this._callback = callback
25
+ this._isOnce = !!isOnce // is Once Listener?
26
+ this._owner = undefined
27
+ }
28
+
29
+ /**
30
+ * Sets the owner of this listener.
31
+ * @param {*} owner - The owner object to be associated with this listener.
32
+ */
33
+ set owner (owner) {
34
+ this._owner = owner
35
+ }
36
+
37
+ get owner () {
38
+ return this._owner === DefaultOwner ? undefined : this._owner
39
+ }
40
+
41
+ get event () {
42
+ return this._event
43
+ }
44
+
45
+ get isOnce () {
46
+ return this._isOnce
47
+ }
48
+
49
+ /**
50
+ * Checks if the provided function is the same as the listener's wrapped function.
51
+ * @param {Function} callback - The function to compare against.
52
+ * @returns {boolean} True if the functions are the same, false otherwise.
53
+ */
54
+ isSameCallback (callback) {
55
+ return this._callback === callback
56
+ }
57
+
58
+ get callback () {
59
+ return this._callback
60
+ }
61
+
62
+ /**
63
+ * Invokes the stored function with the provided arguments.
64
+ * @param {...*} args - Arguments to pass to the function.
65
+ * @returns {*} The result of the function invocation.
66
+ */
67
+ invoke (...args) {
68
+ try {
69
+ return this._callback(...args)
70
+ } finally {
71
+ if (this._isOnce) {
72
+ try {
73
+ this._event._remove(this)
74
+ } catch (err) {
75
+ // do nothing
76
+ console.warn(err)
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Invokes the listener with the provided arguments.
84
+ * Alias for {@linkcode Listener.invoke}
85
+ * @param {...*} args - Arguments to be passed to the listener.
86
+ * @returns {*} The result of the listener invocation.
87
+ */
88
+ listener (...args) {
89
+ return this.invoke(...args)
90
+ }
91
+ }
92
+
93
+ module.exports = Listener
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@creejs/commons-events",
3
+ "version": "1.0.0",
4
+ "description": "Commons EventEmitter",
5
+ "main": "index.js",
6
+ "private": false,
7
+ "files": [
8
+ "index.js",
9
+ "lib/",
10
+ "types/",
11
+ "README.md"
12
+ ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/frameworkee/commons.git"
19
+ },
20
+ "scripts": {
21
+ "dts": "tsc",
22
+ "generate-docs": "node_modules/.bin/jsdoc -c ./jsdoc.json"
23
+ },
24
+ "author": "rodney.vin@gmail.com",
25
+ "license": "Apache-2.0",
26
+ "dependencies": {
27
+ "@creejs/commons-lang": "^1.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "better-docs": "^2.7.3",
31
+ "jsdoc": "^4.0.4"
32
+ }
33
+ }