This package provides a flexible API for creating an event emitter that forces events to execute and resolve in the order in which they are called. This emitter supports both synchronous and asynchronous event handling, ensuring that events are processed sequentially. Additionally, subscriptions are supported and can be triggered from these events.
- function createEmitter(config: T): Emitter
- emitter.subscribe(subscription: Subscription): () => void
- emitter.enable(): void
- emitter.disable(): void
- emitter.enabled: boolean
- emitter.flushing: boolean
- emitter.initialized: boolean
npm install @stoutmusclenugget/create-emitter
yarn add @stoutmusclenugget/create-emitter
pnpm add @stoutmusclenugget/create-emitter
import { createEmitter, type CreateEmitter } from '@stoutmusclenugget/create-emitter';
Call the createEmitter()
function by passing in a configuration object - a configuration object can map any value to its keys, including: functions, objects, primitives, etc.
import { createEmitter } from '@stoutmusclenugget/create-emitter';
const emitter = createEmitter({
async asynchronousMethod() {},
synchronousMethod() {},
staticNumber: 1,
staticString: 'example',
staticObject: {},
});
You then have access to all of the methods and properties passed from the configuration object. Of note, the signature of each method and property remains unchanged. Methods are wrapped in a special queueing functionality that allows us to fire events off in sequential order to avoid race conditions.
await emitter.asynchronousMethod(); // Promise<void>
emitter.synchronousMethod(); // void
emitter.staticNumber; // 1
emitter.staticString; // 'example'
emitter.staticObject; // {}
In addition to the methods and properties you passed in, you now have access to the following methods and properties, which I will explain in depth further down:
-
const unsubscribe = emitter.subscribe()
: Allows you to subscribe to any method you passed into the initial configuration object. -
emitter.enable();
: Enables subscriptions to be triggered when their associated methods are called. -
emitter.disable();
: Disables subscriptions to method calls. -
emitter.enabled;
: A boolean value that tells you if subscriptions are enabled/disabled. -
emitter.flushing;
: A boolean value that tells you if the event queue is being flushed. -
emitter.initialized;
: A boolean value that tells you if the emitter has been intitialized.
This function is used to create an emitter. You pass in a configuration object that maps keys to any other value. If any number of functions are passed in, under the hood we wrap these values in special logic that forces the functions to be executed in the order they were called. Furthermore, you can subscribe to any of these functions to trigger side-effects of your own.
- The config parameter is an object that can contain methods, properties, and an optional
initialize
method. - If provided, the
initialize
function when triggered will be executed once before the first interaction with the emitter. - Returns an
Emitter
object that includes the same methods and properties as those defined in the config object. Additionally, it includes methods for subscribing to events and managing the emitter's internal state. - All methods are processed sequentially to avoid race conditions by maintaining an internal queue to manage the order of event execution.
import { createEmitter } from '@stoutmusclenugget/create-emitter';
const emitter = createEmitter({
async asynchronousMethod() {},
synchronousMethod() {},
staticNumber: 1,
staticString: 'example',
staticObject: {},
});
await emitter.asynchronousMethod(); // Promise<void>
emitter.synchronousMethod(); // void
emitter.staticNumber; // 1
emitter.staticString; // 'example'
emitter.staticObject; // {}
emitter.subscribe(subscription:
Subscription<T>): () => void
To trigger side-effects when using the createEmitter function, you can use the subscribe method provided by the emitter. This method allows you to define functions that will be called whenever the corresponding methods in the emitter are triggered. You can also use the all
and catch
handlers to handle all events and errors, respectively.
- When you call
emitter.subscribe()
, an unsubcribe function is returned. - Anything you return from a config method will get passed directly to each subscription as its first argument.
- Any arguments to a config method will get passed directly to each subscription as its subsequent arguments.
- If you call the
unsubscribe()
function that is returned, the subscription will be removed and no longer get triggered. - You can subscribe to all events using a single handler with the key
all
. - You can subscribe to any errors that get thrown within methods using the key
catch
.
import { createEmitter } from '@stoutmusclenugget/create-emitter';
// Whatever you return from a method then gets passed to each subscription as the first argument.
const emitter = createEmitter({
async asynchronousMethod() {
return 'asynchronousMethod';
},
synchronousMethod() {
return 'synchronousMethod';
},
});
// When you subscribe, you receive an unsubscribe() function as a return value.
const unsubscribe = emitter.subscribe({
asynchronousMethod(method) {
console.log(`Executing `${method}` subscription...`);
},
synchronousMethod(method) {
console.log(`Executing `${method}` subscription...`);
},
});
emitter.asynchronousMethod(); // log: 'Executing asynchronousMethod subscription...'
emitter.synchronousMethod(); // log: 'Executing synchronousMethod subscription...'
unsubscribe(); // Unsubscribing removes the subscription so it is no longer executed.
emitter.asynchronousMethod(); // Nothing logs.
emitter.synchronousMethod(); // Nothing logs.
The emitter can be enabled using the enable()
method. When enabled, subscriptions are triggered. Subscriptions are enabled by default.
const emitter = createEmitter({
method() {},
});
emitter.subscribe({
method() {
console.log(`Executing method subscription...`);
},
});
emitter.disable();
emitter.method(); // Logs Nothing.
emitter.enable();
emitter.method(); // log: 'Executing method subscription...'
The emitter can be disabled using the disable()
method. When disabled, subscriptions are not triggered.
const emitter = createEmitter({
method() {},
});
emitter.subscribe({
method() {
console.log(`Executing method subscription...`);
},
});
emitter.method(); // log: 'Executing method subscription...'
emitter.disable();
emitter.method(); // Logs nothing.
type Fn = (...args: any) => any;
type Config = Record<string, any> & { initialize?: Fn };
type Subscription<C extends ConditionalPick<Config, Fn>, Key extends keyof C = keyof C> = Readonly<
{
[Key in keyof C]?: (payload: AsyncReturnType<C[Key]>, ...args: Parameters<C[Key]>) => unknown;
} & {
all?<K extends Key>(key: K, payload: AsyncReturnType<C[K]>): unknown;
all?<K extends Key>(key: K, payload: AsyncReturnType<C[K]>, ...args: Parameters<C[K]>): unknown;
catch?<K extends Key>(key: K, payload: Error): unknown;
catch?<K extends Key>(key: K, payload: Error, ...args: Parameters<C[K]>): unknown;
}
>;
type Emitter<C extends Config> = C & {
__SUBSCRIPTIONS__: Map<symbol, Subscription<C>>;
get enabled(): boolean;
get flushing(): boolean;
get initialized(): boolean;
disable(): void;
enable(): void;
subscribe(subscription: Subscription<ConditionalPick<C, Fn>>): () => void;
};
This project is licensed under the MIT License.