@irrelon/emitter 4.0.1 → 5.0.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/src/Emitter.ts ADDED
@@ -0,0 +1,719 @@
1
+ /*
2
+ The MIT License (MIT)
3
+
4
+ Copyright (c) 2014 Irrelon Software Limited
5
+ https://www.irrelon.com
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice, url and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
24
+
25
+ Source: https://github.com/irrelon/emitter
26
+
27
+ Changelog:
28
+ Version 5.0.2:
29
+ Removed some unnecessary code, updated to be in step with version
30
+ that is being used in the new version of Isogenic Game Engine.
31
+ Version 5.0.1:
32
+ Further TypeScript updates.
33
+ Version 5.0.0:
34
+ Added TypeScript and prettier, removed babel etc
35
+ Version 4.0.1:
36
+ Updated library to use new ES6 functionality making Overload()
37
+ less useful so it can be removed as a dependency.
38
+ Version 4.0.0:
39
+ Breaking change. Library now has named exports `Emitter` and
40
+ `makeEmitter`. `Emitter` is an ES6 class that can be extended
41
+ and `makeEmitter` is a function that takes an object or class
42
+ and does what `Emitter(someObject)` used to do. This change is
43
+ to support being able to extend `Emitter` as a base class.
44
+ Version 3.1.0:
45
+ Changed order of execution so that listeners that are listening
46
+ against a specific ID get called before the general catch-all
47
+ listeners. Renamed package to @irrelon/emitter.
48
+ Version 2.0.11:
49
+ Added cancelStatic method to allow cancelling a static event
50
+ Version 2.0.7:
51
+ Fixed UMD module support
52
+ Version 2.0.6:
53
+ Added UMD module support
54
+ Version 2.0.5:
55
+ Added bower version number
56
+ Version 2.0.4:
57
+ Allow instantiation as independent instance, updated unit tests
58
+ Version 2.0.3:
59
+ Documentation updates, published to bower
60
+ Version 2.0.2:
61
+ Documentation updates
62
+ Version 2.0.1:
63
+ Bug fix in this._emitters usage
64
+ Version 2.0.0:
65
+ Big update to bring in line with latest developments in other projects. Event emitter can
66
+ now use deferEmit(), emitId(), emitStatic(), emitStaticId(), willEmit(), willEmitId().
67
+ Version 1.1.9:
68
+ Updated changelog correctly
69
+ Version 1.1.8:
70
+ Removed tons of dependencies wrongly included in main dependencies, have moved to devDependencies section of package.json
71
+ Version 1.1.0:
72
+ Added support for overloaded methods
73
+ Added support for events with ids
74
+ Version 1.0.2:
75
+ Removed AMD support, added browserify support
76
+ Added package.json
77
+ Added once() method
78
+ Added hasListener() method
79
+ Published to NPM as irrelon-emitter
80
+ Version 1.0.1:
81
+ Added ability to extend any object with eventing capability
82
+ Added AMD / Require.js support
83
+ Added Node.js support
84
+ Version 1.0.0:
85
+ First commit
86
+ */
87
+ export enum EventReturnFlag {
88
+ none,
89
+ cancel
90
+ }
91
+
92
+ export type EventListenerCallback = (...args: any[]) => any | Promise<any>;
93
+ export interface EventStaticEmitterObject {
94
+ id: string;
95
+ args: any[];
96
+ }
97
+
98
+ /**
99
+ * Creates a new class with the capability to emit events.
100
+ */
101
+ export class Emitter {
102
+ _eventsEmitting: boolean = false;
103
+ _eventRemovalQueue: any[] = [];
104
+ _eventListeners?: Record<string, Record<string, EventListenerCallback[]>>;
105
+ _eventStaticEmitters: Record<string, EventStaticEmitterObject[]> = {};
106
+ _eventsAllowDefer: boolean = false;
107
+ _eventsDeferTimeouts: Record<any, number> = {};
108
+
109
+ /**
110
+ * Attach an event listener to the passed event only if the passed
111
+ * id matches the id for the event being fired.
112
+ * @param {string} eventName The name of the event to listen for.
113
+ * @param {string} id The id to match against.
114
+ * @param {function} listener The method to call when the event is fired.
115
+ * @returns {Emitter} The emitter instance.
116
+ */
117
+ _on (eventName: string, id: string, listener: EventListenerCallback) {
118
+ const generateTimeout = (emitter: EventStaticEmitterObject) => {
119
+ setTimeout(() => {
120
+ listener(...emitter.args);
121
+ }, 1);
122
+ };
123
+
124
+ this._eventListeners = this._eventListeners || {};
125
+ this._eventListeners[eventName] = this._eventListeners[eventName] || {};
126
+ this._eventListeners[eventName][id] = this._eventListeners[eventName][id] || [];
127
+ this._eventListeners[eventName][id].push(listener);
128
+
129
+ // Check for any static emitters, and fire the event if any exist
130
+ if (!this._eventStaticEmitters || !this._eventStaticEmitters[eventName] || !this._eventStaticEmitters[eventName].length) return this;
131
+
132
+ // Emit events for each emitter
133
+ for (let i = 0; i < this._eventStaticEmitters[eventName].length; i++) {
134
+ const emitter = this._eventStaticEmitters[eventName][i];
135
+
136
+ if (id === "*" || emitter.id === id) {
137
+ // Call the listener out of process so that any code that expects a listener
138
+ // to be called at some point in the future rather than immediately on registration
139
+ // will not fail
140
+ generateTimeout(emitter);
141
+ }
142
+ }
143
+
144
+ return this;
145
+ }
146
+
147
+ /**
148
+ * Attach an event listener to the passed event only if the passed
149
+ * id matches the document id for the event being fired.
150
+ * @param {String} eventName The name of the event to listen for.
151
+ * @param {*} id The id to match against.
152
+ * @param {Function} listener The method to call when the event is fired.
153
+ * @returns {Emitter} The emitter instance.
154
+ */
155
+ _once (eventName: string, id: string, listener: EventListenerCallback) {
156
+ let fired = false;
157
+
158
+ const internalCallback = (...args: any[]) => {
159
+ if (fired) return;
160
+
161
+ fired = true;
162
+ this.off(eventName, id, internalCallback);
163
+ listener(...args);
164
+ };
165
+
166
+ return this.on(eventName, id, internalCallback);
167
+ }
168
+
169
+ /**
170
+ * Cancels an event listener based on an event name, id and listener function.
171
+ * @param {String} eventName The event to cancel listener for.
172
+ * @param {String} id The ID of the event to cancel listening for.
173
+ * @param {Function} listener The event listener function used in the on()
174
+ * or once() call to cancel.
175
+ * @returns {Emitter} The emitter instance.
176
+ */
177
+ _off (eventName: string, id: string, listener?: EventListenerCallback): this {
178
+ // If the event name doesn't have any listeners, exit early
179
+ if (!this._eventListeners || !this._eventListeners[eventName] || !this._eventListeners[eventName][id]) return this;
180
+
181
+ // If we are emitting events at the moment, don't remove this listener
182
+ // until the process has completed, so we queue for removal instead
183
+ if (this._eventsEmitting) {
184
+ this._eventRemovalQueue = this._eventRemovalQueue || [];
185
+ this._eventRemovalQueue.push(() => {
186
+ this.off(eventName, id, listener);
187
+ });
188
+
189
+ return this;
190
+ }
191
+
192
+ // Check if we have no specific listener... in this case
193
+ // we want to remove all listeners for an id
194
+ if (!listener) {
195
+ if (id === "*") {
196
+ // The id is "all" and no listener was provided so delete all
197
+ // event listeners for this event name
198
+ delete this._eventListeners[eventName];
199
+ return this;
200
+ }
201
+
202
+ // No listener provided, delete all listeners for this id
203
+ delete this._eventListeners[eventName][id];
204
+ return this;
205
+ }
206
+
207
+ const arr = this._eventListeners[eventName][id] || [];
208
+ const index = arr.indexOf(listener);
209
+
210
+ if (index > -1) {
211
+ arr.splice(index, 1);
212
+ }
213
+
214
+ return this;
215
+ }
216
+
217
+ /**
218
+ * Attach an event listener to the passed event only if the passed
219
+ * id matches the document id for the event being fired.
220
+ * @param {String} eventName The name of the event to listen for.
221
+ * @param {*} id The id to match against.
222
+ * @param {Function} listener The method to call when the event is fired.
223
+ * @returns {Emitter} The emitter instance.
224
+ */
225
+ on (eventName: string, id: string, listener: EventListenerCallback): this;
226
+ on (eventName: string, listener: EventListenerCallback): this;
227
+ on (eventName: string, ...rest: any[]): this {
228
+ const restTypes = rest.map((arg) => typeof arg);
229
+
230
+ if (restTypes[0] === "function") {
231
+ return this._on(eventName, "*", rest[0]);
232
+ }
233
+
234
+ return this._on(eventName, rest[0], rest[1]);
235
+ }
236
+
237
+ /**
238
+ * Attach an event listener to the passed event only if the passed
239
+ * id matches the document id for the event being fired.
240
+ * @param {String} eventName The name of the event to listen for.
241
+ * @param id The id to match against.
242
+ * @param listener
243
+ * @returns {Emitter} The emitter instance.
244
+ */
245
+ once (eventName: string, id: string, listener: EventListenerCallback): this;
246
+ once (eventName: string, listener: EventListenerCallback): this;
247
+ once (eventName: string, ...rest: any[]): this {
248
+ const restTypes = rest.map((arg) => typeof arg);
249
+
250
+ if (restTypes[0] === "function") {
251
+ return this._once(eventName, "*", rest[0]);
252
+ }
253
+
254
+ return this._once(eventName, rest[0], rest[1]);
255
+ }
256
+
257
+ /**
258
+ * Overwrites any previous event listeners so that if the event fires, only
259
+ * the listener you pass will be called. If you pass an id along with the
260
+ * listener, only listeners for the id will be overwritten. This is similar
261
+ * to calling `.off(eventName)` then `on(eventName, listener)` since
262
+ * `off(eventName)` will cancel any previous event listeners for the passed
263
+ * event name.
264
+ * @param {String} eventName The name of the event to listen for.
265
+ * @param {*} id The id to match against.
266
+ * @param {Function} listener The method to call when the event is fired.
267
+ * @returns {Emitter} The emitter instance.
268
+ */
269
+ overwrite (eventName: string, id: string, listener: EventListenerCallback): this;
270
+ overwrite (eventName: string, listener: EventListenerCallback): this;
271
+ overwrite (eventName: string, ...rest: any[]) {
272
+ const restTypes = rest.map((arg) => typeof arg);
273
+
274
+ if (restTypes[0] === "function") {
275
+ this.off(eventName);
276
+ return this._on(eventName, "*", rest[0]);
277
+ }
278
+
279
+ this.off(eventName, rest[0]);
280
+ return this._on(eventName, rest[0], rest[1]);
281
+ }
282
+
283
+ /**
284
+ * Cancels an event listener based on an event name, id and listener function.
285
+ * @param {String} eventName The event to cancel listener for.
286
+ * @param {String} id The ID of the event to cancel listening for.
287
+ * @param {Function} listener The event listener function used in the on()
288
+ * or once() call to cancel.
289
+ * @returns {Emitter} The emitter instance.
290
+ */
291
+ off (eventName: string, id: string, listener?: EventListenerCallback): this;
292
+ off (eventName: string, listener?: EventListenerCallback): this;
293
+ off (eventName: string): this;
294
+ off (eventName: string, ...rest: any[]) {
295
+ if (rest.length === 0) {
296
+ // Only event was provided, use * as the id to mean "any without"
297
+ // a specific id
298
+ return this._off(eventName, "*");
299
+ }
300
+
301
+ const restTypes = rest.map((arg) => typeof arg);
302
+
303
+ if (restTypes[0] === "function") {
304
+ // The first arg after the event name was a function (listener)
305
+ // so remove listening for events for this specific listener
306
+ return this._off(eventName, "*", rest[0]);
307
+ }
308
+
309
+ // Both id and listener were provided, remove for the specific id
310
+ return this._off(eventName, rest[0], rest[1]);
311
+ }
312
+
313
+ /**
314
+ * Emit an event by name.
315
+ * @param {Object} eventName The name of the event to emit.
316
+ * @param {...any} data The arguments to send to any listening methods.
317
+ * If you are sending multiple arguments, separate them with a comma so
318
+ * that they are received by the function as separate arguments.
319
+ * @return {number}
320
+ * @example #Emit an Event
321
+ * // Emit the event named "hello"
322
+ * myEntity.emit('hello');
323
+ * @example #Emit an Event With Data Object
324
+ * // Emit the event named "hello"
325
+ * myEntity.emit('hello', {moo: true});
326
+ * @example #Emit an Event With Multiple Data Values
327
+ * // Emit the event named "hello"
328
+ * myEntity.emit('hello', {moo: true}, 'someString');
329
+ * @example #Listen for Event Data
330
+ * // Set a listener to listen for the data (multiple values emitted
331
+ * // from an event are passed as function arguments)
332
+ * myEntity.on('hello', function (arg1, arg2) {
333
+ * console.log(arg1, arg2);
334
+ * }
335
+ *
336
+ * // Emit the event named "hello"
337
+ * myEntity.emit('hello', 'data1', 'data2');
338
+ *
339
+ * // The console output is:
340
+ * // data1, data2
341
+ */
342
+ async emit (eventName: string, ...data: any[]): Promise<EventReturnFlag> {
343
+ if (!this._eventListeners) {
344
+ return EventReturnFlag.none;
345
+ }
346
+
347
+ const id = "*";
348
+
349
+ let returnFlag: EventReturnFlag = EventReturnFlag.none;
350
+ this._eventsEmitting = true;
351
+
352
+ if (this._eventListeners[eventName] && this._eventListeners[eventName][id]) {
353
+ // Handle global emit
354
+ const arr = this._eventListeners[eventName][id];
355
+
356
+ const promiseArr = arr.map((tmpFunc) => {
357
+ if (typeof tmpFunc !== "function") return;
358
+ return tmpFunc(...data);
359
+ });
360
+
361
+ const result = (await Promise.all(promiseArr)).find((tmpResult) => tmpResult !== EventReturnFlag.none);
362
+
363
+ if (result) {
364
+ returnFlag = EventReturnFlag.cancel;
365
+ }
366
+ }
367
+
368
+ this._eventsEmitting = false;
369
+ this._processRemovalQueue();
370
+
371
+ return returnFlag;
372
+ }
373
+
374
+ emitId (eventName: string, id: string, ...data: any[]): EventReturnFlag {
375
+ if (!this._eventListeners) {
376
+ return EventReturnFlag.none;
377
+ }
378
+
379
+ this._eventListeners = this._eventListeners || {};
380
+ this._eventsEmitting = true;
381
+
382
+ if (!this._eventListeners[eventName]) {
383
+ this._eventsEmitting = false;
384
+ this._processRemovalQueue();
385
+
386
+ return EventReturnFlag.none;
387
+ }
388
+
389
+ // Handle id emit
390
+ if (this._eventListeners[eventName][id]) {
391
+ const arr = this._eventListeners[eventName][id];
392
+ const arrCount = arr.length;
393
+
394
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
395
+ // Check we have a function to execute
396
+ const tmpFunc = arr[arrIndex];
397
+
398
+ if (typeof tmpFunc === "function") {
399
+ tmpFunc.call(this, ...data);
400
+ }
401
+ }
402
+ }
403
+
404
+ // Handle global emit
405
+ if (this._eventListeners[eventName]["*"]) {
406
+ const arr = this._eventListeners[eventName]["*"];
407
+ const arrCount = arr.length;
408
+
409
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
410
+ // Check we have a function to execute
411
+ const tmpFunc = arr[arrIndex];
412
+
413
+ if (typeof tmpFunc === "function") {
414
+ tmpFunc.call(this, ...data);
415
+ }
416
+ }
417
+ }
418
+
419
+ this._eventsEmitting = false;
420
+ this._processRemovalQueue();
421
+
422
+ return EventReturnFlag.none;
423
+ }
424
+
425
+ /**
426
+ * Creates a persistent emitter record that will fire a listener if
427
+ * one is added for this event after the emitStatic() call has been
428
+ * made.
429
+ * @param {String} eventName The name of the event to emit.
430
+ * @param {...any} data Optional arguments to emit with the event.
431
+ * @returns {Emitter} The emitter instance.
432
+ * @private
433
+ */
434
+ emitStatic (eventName: string, ...data: any[]) {
435
+ const id = "*";
436
+ this._eventListeners = this._eventListeners || {};
437
+ this._eventsEmitting = true;
438
+
439
+ if (this._eventListeners[eventName] && this._eventListeners[eventName][id]) {
440
+ // Handle global emit
441
+ const arr = this._eventListeners[eventName][id];
442
+ const arrCount = arr.length;
443
+
444
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
445
+ // Check we have a function to execute
446
+ const tmpFunc = arr[arrIndex];
447
+
448
+ if (typeof tmpFunc === "function") {
449
+ tmpFunc.call(this, ...data);
450
+ }
451
+ }
452
+ }
453
+
454
+ this._eventsEmitting = false;
455
+
456
+ this._eventStaticEmitters = this._eventStaticEmitters || {};
457
+ this._eventStaticEmitters[eventName] = this._eventStaticEmitters[eventName] || [];
458
+ this._eventStaticEmitters[eventName].push({
459
+ "id": "*",
460
+ "args": data
461
+ });
462
+
463
+ this._processRemovalQueue();
464
+
465
+ return this;
466
+ }
467
+
468
+ /**
469
+ * Creates a persistent emitter record that will fire a listener if
470
+ * one is added for this event after the emitStatic() call has been
471
+ * made.
472
+ * @param {String} eventName The name of the event to emit.
473
+ * @param {String} id The id of the event to emit.
474
+ * @param {...any} data Optional arguments to emit with the event.
475
+ * @returns {Emitter} The emitter instance.
476
+ * @private
477
+ */
478
+ emitStaticId (eventName: string, id: string, ...data: any[]) {
479
+ if (!id) throw new Error("Missing id from emitId call!");
480
+
481
+ this._eventListeners = this._eventListeners || {};
482
+ this._eventsEmitting = true;
483
+
484
+ if (this._eventListeners[eventName]) {
485
+ // Handle id emit
486
+ if (this._eventListeners[eventName][id]) {
487
+ const arr = this._eventListeners[eventName][id];
488
+ const arrCount = arr.length;
489
+
490
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
491
+ // Check we have a function to execute
492
+ const tmpFunc = arr[arrIndex];
493
+
494
+ if (typeof tmpFunc === "function") {
495
+ tmpFunc.call(this, ...data);
496
+ }
497
+ }
498
+ }
499
+
500
+ // Handle global emit
501
+ if (this._eventListeners[eventName]["*"]) {
502
+ const arr = this._eventListeners[eventName]["*"];
503
+ const arrCount = arr.length;
504
+
505
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
506
+ // Check we have a function to execute
507
+ const tmpFunc = arr[arrIndex];
508
+
509
+ if (typeof tmpFunc === "function") {
510
+ tmpFunc.call(this, ...data);
511
+ }
512
+ }
513
+ }
514
+ }
515
+
516
+ this._eventsEmitting = false;
517
+
518
+ this._eventStaticEmitters = this._eventStaticEmitters || {};
519
+ this._eventStaticEmitters[eventName] = this._eventStaticEmitters[eventName] || [];
520
+ this._eventStaticEmitters[eventName].push({
521
+ id,
522
+ "args": data
523
+ });
524
+
525
+ this._processRemovalQueue();
526
+
527
+ return this;
528
+ }
529
+
530
+ /**
531
+ * Handles removing emitters, is an internal method not called directly.
532
+ * @param {String} eventName The event to remove static emitter for.
533
+ * @returns {Emitter} The emitter instance.
534
+ * @private
535
+ */
536
+ cancelStatic (eventName: string) {
537
+ this._eventStaticEmitters = this._eventStaticEmitters || {};
538
+ this._eventStaticEmitters[eventName] = [];
539
+
540
+ return this;
541
+ }
542
+
543
+ /**
544
+ * Checks if an event has any event listeners or not.
545
+ * @param {String} eventName The name of the event to check for.
546
+ * @returns {boolean} True if one or more event listeners are registered for
547
+ * the event. False if none are found.
548
+ */
549
+ willEmit (eventName: string) {
550
+ const id = "*";
551
+
552
+ if (!this._eventListeners || !this._eventListeners[eventName]) {
553
+ return false;
554
+ }
555
+
556
+ const arr = this._eventListeners[eventName][id];
557
+ const arrCount = arr.length;
558
+
559
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
560
+ // Check we have a function to execute
561
+ const tmpFunc = arr[arrIndex];
562
+
563
+ if (typeof tmpFunc === "function") {
564
+ return true;
565
+ }
566
+ }
567
+
568
+ return false;
569
+ }
570
+
571
+ /**
572
+ * Checks if an event has any event listeners or not based on the passed id.
573
+ * @param {String} eventName The name of the event to check for.
574
+ * @param {String} id The event ID to check for.
575
+ * @returns {boolean} True if one or more event listeners are registered for
576
+ * the event. False if none are found.
577
+ */
578
+ willEmitId (eventName: string, id: string) {
579
+ if (!this._eventListeners || !this._eventListeners[eventName]) {
580
+ return false;
581
+ }
582
+
583
+ // Handle id emit
584
+ if (this._eventListeners[eventName][id]) {
585
+ const arr = this._eventListeners[eventName][id];
586
+ const arrCount = arr.length;
587
+
588
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
589
+ // Check we have a function to execute
590
+ const tmpFunc = arr[arrIndex];
591
+
592
+ if (typeof tmpFunc === "function") {
593
+ return true;
594
+ }
595
+ }
596
+ }
597
+
598
+ // Handle global emit
599
+ if (this._eventListeners[eventName]["*"]) {
600
+ const arr = this._eventListeners[eventName]["*"];
601
+ const arrCount = arr.length;
602
+
603
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
604
+ // Check we have a function to execute
605
+ const tmpFunc = arr[arrIndex];
606
+
607
+ if (typeof tmpFunc === "function") {
608
+ return true;
609
+ }
610
+ }
611
+ }
612
+
613
+ return false;
614
+ }
615
+
616
+ /**
617
+ * Queues an event to be fired. This has automatic de-bouncing so that any
618
+ * events of the same type that occur within 100 milliseconds of a previous
619
+ * one will all be wrapped into a single emit rather than emitting tons of
620
+ * events for lots of chained inserts etc. Only the data from the last
621
+ * de-bounced event will be emitted.
622
+ * @param {String} eventName The name of the event to emit.
623
+ * @param {...any} data Optional arguments to emit with the event.
624
+ * @returns {Emitter} The emitter instance.
625
+ */
626
+ deferEmit (eventName: string, ...data: any[]) {
627
+ if (!this._eventsAllowDefer) {
628
+ // Check for an existing timeout
629
+ this._eventsDeferTimeouts = this._eventsDeferTimeouts || {};
630
+
631
+ if (this._eventsDeferTimeouts[eventName]) {
632
+ clearTimeout(this._eventsDeferTimeouts[eventName]);
633
+ }
634
+
635
+ // Set a timeout
636
+ this._eventsDeferTimeouts[eventName] = setTimeout(() => {
637
+ this.emit.call(this, eventName, ...data);
638
+ }, 1) as unknown as number;
639
+ } else {
640
+ this.emit.call(this, eventName, ...data);
641
+ }
642
+
643
+ return this;
644
+ }
645
+
646
+ /**
647
+ * If events are cleared with the off() method while the event emitter is
648
+ * actively processing any events then the off() calls get added to a
649
+ * queue to be executed after the event emitter is finished. This stops
650
+ * errors that might occur by potentially modifying the event queue while
651
+ * the emitter is running through them. This method is called after the
652
+ * event emitter is finished processing.
653
+ * @private
654
+ */
655
+ _processRemovalQueue () {
656
+ if (!this._eventRemovalQueue || !this._eventRemovalQueue.length) {
657
+ return;
658
+ }
659
+
660
+ // Execute each removal call
661
+ for (let i = 0; i < this._eventRemovalQueue.length; i++) {
662
+ this._eventRemovalQueue[i]();
663
+ }
664
+
665
+ // Clear the removal queue
666
+ this._eventRemovalQueue = [];
667
+ }
668
+ }
669
+
670
+ export function makeEmitter(obj: new (...args: any[]) => any, prototypeMode: boolean): Emitter;
671
+ export function makeEmitter(obj: boolean): Emitter;
672
+ export function makeEmitter(obj: Record<string, unknown>, prototypeMode: boolean): Emitter;
673
+ export function makeEmitter (obj: any, prototypeMode?: any) {
674
+ let operateOnObject;
675
+
676
+ if (obj === undefined && prototypeMode === undefined) {
677
+ obj = {};
678
+ prototypeMode = false;
679
+ }
680
+
681
+ if (typeof obj === "boolean" && prototypeMode === undefined) {
682
+ prototypeMode = obj;
683
+ obj = {};
684
+ }
685
+
686
+ if (prototypeMode === undefined) {
687
+ prototypeMode = true;
688
+ }
689
+
690
+ if (typeof obj !== "object" && typeof obj !== "function") {
691
+ throw new Error("Cannot operate on a non-object / non-function passed as first argument!");
692
+ }
693
+
694
+ if (prototypeMode) {
695
+ if (obj.prototype === undefined) {
696
+ throw new Error("Cannot modify prototype of passed object, it has no prototype property! Was it instantiated with the new operator correctly?");
697
+ }
698
+
699
+ operateOnObject = obj.prototype;
700
+ } else {
701
+ operateOnObject = obj;
702
+ }
703
+
704
+ // Convert the object prototype to have eventing capability
705
+ operateOnObject.on = Emitter.prototype.on;
706
+ operateOnObject.off = Emitter.prototype.off;
707
+ operateOnObject.once = Emitter.prototype.once;
708
+ operateOnObject.emit = Emitter.prototype.emit;
709
+ operateOnObject.emitId = Emitter.prototype.emitId;
710
+ operateOnObject.emitStatic = Emitter.prototype.emitStatic;
711
+ operateOnObject.emitStaticId = Emitter.prototype.emitStaticId;
712
+ operateOnObject.cancelStatic = Emitter.prototype.cancelStatic;
713
+ operateOnObject.deferEmit = Emitter.prototype.deferEmit;
714
+ operateOnObject.willEmit = Emitter.prototype.willEmit;
715
+ operateOnObject.willEmitId = Emitter.prototype.willEmitId;
716
+ operateOnObject._processRemovalQueue = Emitter.prototype._processRemovalQueue;
717
+
718
+ return obj;
719
+ }