typescript-event-emitter
Version:
Versatile and feature-rich TypeScript library for event management, providing a solid foundation for building event-driven applications in TypeScript.
549 lines (435 loc) • 16.4 kB
Markdown
# typescript-event-emitter
Versatile and feature-rich TypeScript library for event management, providing a solid foundation for building event-driven applications in TypeScript.
| \* | Version | Supported |
| --- | --------- | ------------------ |
| npm | >= 7.24.0 | :white_check_mark: |
# main features
1. Throttling and Debouncing:
- flexibility for handling events in scenarios where rapid or frequent triggering needs to be controlled.
2. Wildcard Listeners:
- supporting wildcard listeners with the '\*' namespace, allowing global event handling.
3. Namespace Support:
- namespace support is a great way to organize and manage different types of events within your system.
4. Priority Queue:
- prioritizing listeners based on a priority value ensures that critical listeners can be given precedence, offering more control over event execution order.
5. Event Filtering:
- the ability to filter events based on registered filters provides a mechanism for selectively emitting events.
6. Async/Await Pattern:
- leveraging async/await for asynchronous operations ensures that it can handle asynchronous listeners gracefully.
7. Global Event Bus Integration:
- the integration with a global event bus enhances the modularity and usability of the event system.
8. Error Handling:
- logging errors to the console.
9. Custom separator per listener and Global configs:
- ability to set custom separator per listener which would override global separator dedicated for listeners.
- ability to change global separator which is used for listeners where separator is not provided.
10. Concurrency:
- Limits the number of concurrent executions for listeners, ensuring efficient handling of multiple events at once.
11. Subscription Management:
- Allows users to easily manage and view their subscriptions to specific event types.
## installation
```bash
$ npm install --save typescript-event-emitter
```
## usage
After installation, the only thing you need to do is require the module:
```bash
import { EventEmitter } from 'typescript-event-emitter';
```
or
```bash
const { EventEmitter } = require('typescript-event-emitter');
```
And you're ready to create your own EventEmitter instances.
### Base usage
```bash
const emitter = new EventEmitter();
let context = { test: 'Some metada' }
const onEventNameEmitted = (eventname:string, data:any) => {
console.log(data === context) // true
console.log(eventname === 'event-name') // true
};
emitter.on('event-name', onEventNameEmitted); // adds listener
emitter.emit('event-name', context); // emits listener
```
### Remove listener
```bash
const emitter = new EventEmitter();
const onEventNameEmitted = (eventname:string, data:any) => {
console.log(eventname,data )
};
emitter.on('event-name', onEventNameEmitted);// adds listener
emitter.off('event-name', onEventNameEmitted); // removes listener
```
### Throttling
1. First emit:
- The 'throttleEvent' is emitted.
- The throttled listener is executed immediately, and callCount becomes 1.
2. Second emit (within the 100-millisecond throttle delay):
- The 'throttleEvent' is emitted again.
- This emit is ignored because throttling prevents the listener from being executed within the 100-millisecond throttle period.
3. Third emit (within the 100-millisecond throttle delay):
- The 'throttleEvent' is emitted once more.
- This emit is also ignored due to throttling.
```bash
const emitter = new EventEmitter();
let callCount = 0;
emitter.on(
'throttleEvent',
() => {
callCount++;
},
{ throttle: 100 }
);
emitter.emit('throttleEvent');
emitter.emit('throttleEvent');
emitter.emit('throttleEvent');
```
### Debouncing
1. First emit:
- The 'debounceEvent' is emitted.
- The debounced listener is called but not immediately executed due to the debounce delay.
- The debounced function is scheduled to be executed after 100 milliseconds.
2. Second emit (within the debounce delay):
- The 'debounceEvent' is emitted.
- The debounced listener is called again, but the previous scheduled execution is canceled, and a new one is scheduled for 100 milliseconds from the latest emit.
3. Third emit (within the debounce delay):
- The 'debounceEvent' is emitted.
- The debounced listener is called once more, canceling the previous scheduled execution again and scheduling a new one for 100 milliseconds from this emit.
So basically for the given example, the listener will be executed after 300 millisecond delay
```bash
const emitter = new EventEmitter();
let callCount = 0;
emitter.on(
'debounceEvent',
() => {
callCount++;
},
{ debounce: 100 }
);
emitter.emit('debounceEvent');
emitter.emit('debounceEvent');
emitter.emit('debounceEvent');
```
### Wildcard
```bash
const emitter = new EventEmitter();
emitter.on('*', () => { // listener will be executed for both emits, wildcard listens to anything
console.log("Executed")
});
emitter.emit('someEvent');
emitter.emit('namespace.someEvent');
```
```bash
const emitter = new EventEmitter();
emitter.on('namespace1.*', () => { // listener will be executed 2 times, wildcard for namespace listens to anything within that namespace
console.log("Executed 1")
});
emitter.on('namespace2.*', () => { // listener will not be executed
console.log("Executed 2")
});
emitter.emit('other.event1');
emitter.emit('namespace1.event1');
emitter.emit('namespace1.event2');
```
```bash
const emitter = new EventEmitter();
emitter.on('*.someEvent', () => { // wildcard listeners as namespace for event
console.log("Executed")
});
emitter.emit('other.event1'); // No match, no listener executed
emitter.emit('other.someEvent'); // Matches the pattern, listener executed
emitter.emit('namespace1.event1'); // No match, no listener executed
emitter.emit('namespace1.someEvent'); // Matches the pattern, listener executed
```
### Namespace
```bash
const emitter = new EventEmitter();
emitter.on('someEvent', () => {
console.log('Listener');
});
emitter.on('namespace.someEvent', () => {
console.log('Listener');
});
emitter.emit('other.event'); // No match, no listener executed
emitter.emit('namespace.someEvent'); // Matches the pattern, listener executed
emitter.emit('namespace.event'); // No match, no listener executed
```
### Priority Queue
```bash
const emitter = new EventEmitter();
//last to be executed
emitter.on('priorityEvent', () => {
console.log('Low Priority Listener');
});
//first to be executed
emitter.on(
'priorityEvent',
() => {
console.log('High Priority Listener');
},
{ priority: 2 }
);
//second to be executed
emitter.on(
'priorityEvent',
() => {
console.log('Medium Priority Listener');
},
{ priority: 1 }
);
emitter.emit('priorityEvent');
```
### Event Filtering
```bash
interface Message {
id: number;
content: string;
messageType: string;
sender: string;
}
const emitter = new EventEmitter();
const currentUser = { username: 'example_user' };
const notificationFilter: EventFilter = (eventName, namespace) => {
if (namespace === 'dm') {
return true;
}
if (eventName === 'notification') {
return true;
}
if (namespace === 'mention' && currentUser.username === eventName) {
return true;
}
return false;
};
const receivedNotifications: Message[] = [];
emitter.on(
'*',
(_event, message: Message) => {
receivedNotifications.push(message); //array will have: directMessage, generalNotification, mentionNotification objects
},
{ filter: notificationFilter }
);
const directMessage: Message = { id: 1, content: 'Hello!', messageType: 'dm', sender: 'user123' };
const generalNotification: Message = {
id: 2,
content: 'General update',
messageType: 'announcement',
sender: 'system'
};
const mentionNotification: Message = {
id: 3,
content: 'You were mentioned!',
messageType: 'mention',
sender: 'other_user'
};
const unrelatedEvent: Message = { id: 4, content: 'Irrelevant event', messageType: 'other', sender: 'unknown' };
emitter.emit('other.event', unrelatedEvent),
emitter.emit('notification', generalNotification),
emitter.emit('dm.newMessage', directMessage),
emitter.emit('mention.example_user', mentionNotification)
```
### Async/Await Pattern
```bash
const emitter = new EventEmitter();
let flag = false;
emitter.on('asyncEvent', async () => {
return new Promise<void>(resolve => {
setTimeout(() => {
flag = true;
resolve();
}, 100);
});
});
await emitter.emit('asyncEvent');
console.log(flag); // will be true
```
### Error Handling
```bash
const emitter = new EventEmitter();
emitter.on('errorEvent', () => {
throw new Error('Listener Error');
});
try {
await emitter.emit('errorEvent');
} catch (error) {
console.log(error) // will be 'Listener Error'
}
```
```bash
const emitter = new EventEmitter();
let firstListenerInvoked = false;
let secondListenerInvoked = false;
emitter.on('errorEvent', () => {
throw new Error('Listener Error');
});
emitter.on('errorEvent', () => {
firstListenerInvoked = true;
});
emitter.on('errorEvent', () => {
secondListenerInvoked = true;
});
emitter.emit('errorEvent'); //all 3 will be fired and event flow won't be disrupted
```
### Global Event Bus
The global event bus is a singleton that contains an instance of an event emitter. Functionality/features, etc is just a centralized mechanism for communication across different parts of an application.
```bash
import { globalEventBus } from 'typescript-event-emitter';
```
or
```bash
const { globalEventBus } = require('typescript-event-emitter');
```
```bash
let context = { test: 'Some metada' }
const onEventNameEmitted = (eventname:string, data:any) => {
console.log(data === context) // true
console.log(eventname === 'event-name') // true
};
globalEventBus.on('event-name', onEventNameEmitted); // adds listener
globalEventBus.emit('event-name', context); // emits listener
```
### Custom separator per listener and Global configs
```bash
const eventEmitter: EventEmitter = new EventEmitter({ separator: ':' }); // setting global separator if not provided it will revert to default "."
eventEmitter.on("namespace:someEvent", () => {});
```
```bash
const eventEmitter: EventEmitter = new EventEmitter(); // default separator '.'
eventEmitter.setGlobalOptions({ separator: "-" }); // sets global separator which can be provided via constructor aswell
eventEmitter.on("namespace-someEvent", () => {});
eventEmitter.off("namespace-someEvent");
eventEmitter.on("namespace:someEvent1", () => {}, { separator: ":" }); // listener separator will be ':'
eventEmitter.off("namespace:someEvent1");
eventEmitter.on("namespace:someEvent2", () => {}, { separator: ":" }); // listener separator will be ':'
eventEmitter.on("namespace-someEvent3", () => {}); // listener separator will be '-' as it was set via setGlobalOptions
eventEmitter.off("namespace:someEvent2");
eventEmitter.off("namespace-someEvent3");
```
```bash
const eventEmitter: EventEmitter = new EventEmitter({ separator: ':' }); // setting global separator if not provided it will revert to default "."
globalEventBus.setGlobalOptions({ separator: "-" }); // sets global separator
globalEventBus.on("namespace:someEvent", () => {}, { separator: ":" }); // listener separator will be ':'
```
### Concurrency
```bash
// Step 1: Create an instance of EventEmitter
const emitter = new EventEmitter();
// Step 2: Add event listeners
emitter.on('data:received', async (eventName, data) => {
console.log(`Listener 1 started processing: ${data}`);
await new Promise(resolve => setTimeout(resolve, 150)); // Simulate processing time
console.log(`Listener 1 finished processing: ${data}`);
}, { concurrency: 2 }); // Limit this listener to 2 concurrent executions
emitter.on('data:received', async (eventName, data) => {
console.log(`Listener 2 started processing: ${data}`);
await new Promise(resolve => setTimeout(resolve, 200)); // Simulate processing time
console.log(`Listener 2 finished processing: ${data}`);
}, { concurrency: 1 }); // Limit this listener to 1 concurrent execution
emitter.on('data:received', async (eventName, data) => {
console.log(`Listener 3 started processing: ${data}`);
await new Promise(resolve => setTimeout(resolve, 250)); // Simulate processing time
console.log(`Listener 3 finished processing: ${data}`);
}, { concurrency: 3 }); // Limit this listener to 3 concurrent executions
// Step 3: Emit an event
console.log('Emitting event: data:received');
await Promise.all([
emitter.emit('data:received', 'Payload 1'),
emitter.emit('data:received', 'Payload 2'),
emitter.emit('data:received', 'Payload 3'),
emitter.emit('data:received', 'Payload 4'),
]);
// Output
// Emitting event: data:received
// Listener 1 started processing: Payload 1
// Listener 2 started processing: Payload 1
// Listener 3 started processing: Payload 1
// Listener 1 started processing: Payload 2
// Listener 3 started processing: Payload 2
// Listener 3 started processing: Payload 3
// Listener 1 finished processing: Payload 1
// Listener 1 started processing: Payload 3
// Listener 1 finished processing: Payload 2
// Listener 1 started processing: Payload 4
// Listener 2 finished processing: Payload 1
// Listener 2 started processing: Payload 2
// Listener 3 finished processing: Payload 1
// Listener 3 started processing: Payload 4
// Listener 3 finished processing: Payload 2
// Listener 3 finished processing: Payload 3
// Listener 1 finished processing: Payload 3
// Listener 1 finished processing: Payload 4
// Listener 2 finished processing: Payload 2
// Listener 2 started processing: Payload 3
// Listener 3 finished processing: Payload 4
// Listener 2 finished processing: Payload 3
// Listener 2 started processing: Payload 4
// Listener 2 finished processing: Payload 4
```
### Subscription Management
```bash
const emitter = new EventEmitter();
const event1 = 'namespace.event1';
const event2 = 'namespace.event2';
const listener1 = () => {};
const listener2 = () => {};
emitter.on(event1, listener1, { priority: 1, concurrency: 5 });
emitter.on(event1, listener2, { priority: 2 });
emitter.on(event2, listener1, { priority: 3 });
const subscriptions = emitter.subscriptions();
// Output
// Lists array of all subscriptions to event key and listener count
//[
// {
// "event":"namespace.event1",
// "listenerCount":2
// },
// {
// "event":"namespace.event2",
// "listenerCount":1
// }
//]
const result1 = emitter.inspectSubscription(event1);
// Output of result1
// {UUID1} & {UUID2} are UUID string
// Lists array of all subscriptions details for particular event
// [
// {
// id: {UUID1},
// priority: 2,
// concurrency: null,
// eventInfo: { separator: '.', event: 'namespace.event1' }
// },
// {
// id: {UUID2},
// priority: 1,
// concurrency: 5,
// eventInfo: { separator: '.', event: 'namespace.event1' }
// }
// ];
// replace {UUID1} with actual id
emitter.removeSubscription(event1, {UUID1});
const result2 = emitter.inspectSubscription(event1);
// Output
// {UUID2} is UUID string
// [
// {
// id: {UUID2},
// priority: 1,
// concurrency: 5,
// eventInfo: { separator: '.', event: 'namespace.event1' }
// }
// ];
```
## Tests
This module is well-tested. You can run:
`npm run test` to run the tests under Node.js.
<br/>
`npm run test:nyc` to run the tests under Node.js and get the coverage
Tests are not included in the npm package. If you want to play with them, you must clone the GitHub repository.
## contributing
Please read our [Contribution Guidelines](CONTRIBUTING.md) before contributing to this project.
## security
Please read our [SECURITY REPORTS](SECURITY.md)
## license
[MIT](LICENSE)