lux-callback-emitter
v1.0.1
Published
Typable event emitter system based on callback functions
Downloads
3
Readme
Why need another customized Event Emitter?
While EventEmitters are a great invention, in my opinion TypeScript still lacks a suitable way to implement a flexible, socket.io-like registration and de-registration of user-defined events in as many application areas as possible. The CallbackEmitter
class is my personal attempted solution to this problem: It not only handles the emitting or invoking, registration and unregistration of callback handlers reliably (without the annoying double registration problems of e.g. addEventHandler) and extends this functionality with the handy Once
of socket.io as well as full and dynamic TypeScript support.
Some of the main strengths include:
- Dynamic typing and callback signature support
- Registration by reference to prevent duplicate listeners
- Listeners to dynamically enable or disable a callback without the need of repeatedly calling
On
andOff
- Handler return value collection
Reference
CallbackEmitter::On
Registers a callback handler for an event name. Registration is done by reference, so multiple registrations won't change anything.
|Parameter|Description|
|-|-|
|event
: string|Event name literal|
|callback
: function|Handler function. Will be executed once the event emits|
If using TypeScript in combination with a
CallbackEmitterEventMap
type, the event name will influence the required callback signature respecting the event map.
CallbackEmitter::Off
Unregisters a callback handler for an event name.
|Parameter|Description|
|-|-|
|event
: string|Event name literal|
|callback
: function|Handler function to unregister|
CallbackEmitter::Once
Analogous to the CallbackEmitter::On
functionality, with the exception that it is immediately unregistered once it has been invoked, thus only being emitted once.
NOTICE: This will not register the provided handler by reference, which may result in duplicate or multiple registrations.
CallbackEmitter::CountListenersOnEvent
Returns a number type representing the current amount of registered handler functions on the given event literal. Please note that this will include Once
registrations.
|Parameter|Description|
|-|-|
|event
: string|Event name literal|
CallbackEmitter::Emit
Emits an event by it's literal. When using a CallbackEmitterEventMap
, the parameters required depend on the handler type declaration for the respective event literal.
If the handler functions return anything, all return values are collected (yes, even those from promises) and returned to the caller as any[]
after all handlers have been invoked.
|Parameter|Description|
|-|-|
|event
: string|Event name literal|
|...params
: any[]|Parameters to pass through handler functions|
CallbackEmitter::GetListener
Once more, this method is analogous to CallbackEmitter:On
, yet instead of registering the supplied callback in silence, it returns a new CallbackEmitterListener
object for the specified event literal.
Types
CallbackEmitterEventMap
Event maps are essential for keeping emitter instances organized and handlers structured correctly. While any CallbackEmitter
can be used without it, providing an event map when constructing the emitter class is the only way to use advanced type linting and autocompletion features.
Example
/**
* "CallbackEmitterEventMap"s are object types that declare
* the handler function signatures for each event literal
*/
type SomeCoolMap =
{
userLogon: (uid: symbol, name: string) => void;
userLogoff: (uid: symbol) => void;
chatMessage: (uidFrom: symbol, msg: string) => void;
}
const chatEmitter = new CallbackEmitter<SomeCoolMap>;
/**
* The use of chatEmitter.On() and other methods will now be
* typed according to their literal assigned function signatures.
**/
CallbackEmitterListener
A listener is an object that contains only one boolean field called enabled
. Updating this field will either register or unregister the callback handler, while defaulting to true
(thus registered) at the start. This can be useful in any context where anonymous functions are required or Once
/Off
are inapplicable options. A particularly common usage is in conjunction with React hooks.
Example
const chatEmitter = new CallbackEmitter<SomeCoolMap>;
function MyReactComponent(props)
{
const [chatMessages, setChatMessages] = useEffect([]);
useEffect(() => {
// Create a listener using the recent state update function
const messageListener =
chatEmitter.GetListener("chatMessage", (id, msg) => {
setChatMessages([...chatMessages, {id, msg}]);
});
// Return an anonymous deactivation function to execute
// once the state updates and the effect re-evaluates
return () => messageListener.enabled = false;
}, [chatMessages])
return <div className="chat-box">
{chatMessages.map(someCoolMessageFormatter)}
</div>
}
Yeah I know, this example seems a bit silly. In reality, the listener functionality would be considerably more beneficial within React class components, for instance. Nevertheless, for the sake of simplicity, this example serves to illustrate the point.
CallbackEmitterOnFunc
and CallbackEmitterEmitFunc
These generic types have been created to streamline the process for developers attempting to enhance the functionality of a CallbackEmitter
from within a custom class or library. They perform the tasks of dynamic typing and function signature matching, eliminating the need for developers to worry about these details.
Example
class OnlyAllowOnButNotEmitBecauseIWantToDoItMyselfClass
{
#emitter = new CallbackEmitter<SomeCoolMap>;
public On: CallbackEmitterOnFunc<SomeCoolMap> =
(...a) => this.#emitter.On(...a)
}
class OnlyAllowEmitBecauseThatDoesNotMykeAnyDamnSenseClass
{
#emitter = new CallbackEmitter<SomeCoolMap>;
public Emit: CallbackEmitterEmitFunc<SomeCoolMap> =
(...a) => this.#emitter.On(...a)
}