metaapi.cloud-sdk
Version:
SDK for MetaApi, a professional cloud forex API which includes MetaTrader REST API and MetaTrader websocket API. Supports both MetaTrader 5 (MT5) and MetaTrader 4 (MT4). CopyFactory copy trading API included. (https://metaapi.cloud)
150 lines (131 loc) • 4.19 kB
text/typescript
;
import LoggerManager from '../logger';
/**
* Custom event emitter implementation for use
*/
class EventEmitter<L extends ListenerSignature<L> = DefaultListener> {
private _listeners: {[E in keyof L]?: Set<L[E]>};
private _eventEmitterLogger = LoggerManager.getLogger('EventEmitter');
/**
* Constructs instance
*/
constructor() {
this._listeners = {};
}
/**
* Subscribes a listener to event
* @param event Event to subscribe to
* @param callback Listener function to subscribe
*/
on<U extends EventEmitter.Event<L>>(event: U, callback: L[U]) {
if (!event) {
throw new Error('Event name is empty or undefined');
}
this._listeners[event] = this._listeners[event] || new Set();
this._listeners[event].add(callback);
}
/**
* Unsubscribes a listener from event
* @param event Event to unsubscribe from
* @param callback Callback to unsubscribe
*/
off<U extends EventEmitter.Event<L>>(event: U, callback: L[U]) {
if (this._listeners[event]) {
this._listeners[event].delete(callback);
if (!this._listeners[event].size) {
delete this._listeners[event];
}
}
}
/**
* Subscribes a listener to event once
* @param event Event to subscribe to
* @param callback Listener function to subscribe
* @param options Additional options
*/
once<U extends EventEmitter.Event<L>>(event: U, callback: L[U], options?: EventEmitter.OnceOptions<L[U]>) {
let listener: any = ((...args: Parameters<L[U]>) => {
if (!options?.ifArgs || options?.ifArgs(...args)) {
this.off(event, listener);
return callback(...args);
}
});
this.on(event, listener);
}
/**
* Emits an event
* @param event Event to emit
* @param data Event payload
* @returns Promise resolving when all listeners calls completed. Usually async version is useful if listeners may
* return promises and the call should wait for them. If one of listener rejects or throws an error, it will be
* logged. Async listeners called all at once
*/
async emit<U extends EventEmitter.Event<L>>(event: U, ...args: Parameters<L[U]>): Promise<void> {
if (!this._listeners[event]?.size) {
return;
}
let results = [];
for (let listener of this._listeners[event]) {
results.push(this._callListener(event as string, listener, args));
}
return Promise.all(results) as any;
}
/**
* Returns subscribed events
* @returns subscribed events
*/
getSubscriptions(): EventEmitter.Event<L>[] {
return Object.keys(this._listeners) as EventEmitter.Event<L>[];
}
/**
* Returns subscribed event listeners
* @param event Event
* @returns Listeners
*/
getListeners<U extends EventEmitter.Event<L>>(event: U): L[U][] {
if (!this._listeners[event]?.size) {
return [];
}
return [...this._listeners[event].values()];
}
/**
* Returns whether has listeners on specific event
* @param event Event
* @returns Whether has listeners
*/
hasListeners<U extends EventEmitter.Event<L>>(event: U): boolean {
return !!this._listeners[event];
}
private async _callListener(event: string, listener: (...args: any) => any, args: any[]) {
try {
await listener(...args);
} catch (err) {
this._eventEmitterLogger.error(`${event}: listener failed with error`, err);
}
}
}
namespace EventEmitter {
/** Method options */
export type OnceOptions<Listener extends (...args: any[]) => any> = {
/**
* If specified, the listener will be called only if the callback returns true. Otherwise, the listener will wait
* for the next matching call
*/
ifArgs?: (...args: Parameters<Listener>) => boolean;
};
/** Infers event type of a listener */
export type Event<L extends ListenerSignature<L>> = Extract<keyof L, string>;
}
export default EventEmitter;
/**
* Event emitter listener type
*/
export type ListenerSignature<Listeners> = {
[EventName in keyof Listeners]: (...args: any[]) => any;
};
/**
* Default event emitter listener type
*/
export type DefaultListener = {
[k: string]: (...args: any[]) => any;
};