@malagu/core
Version:
125 lines (106 loc) • 4.09 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Disposable } from './disposable';
/**
* Represents a typed event.
*/
export interface Event<T> {
/**
*
* @param listener The listener function will be call when the event happens.
* @param thisArgs The 'this' which will be used when calling the event listener.
* @param disposables An array to which a {{IDisposable}} will be added.
* @return a disposable to remove the listener again.
*/
(listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]): Disposable;
/**
* An emitter will print a warning if more listeners are added for this event.
* The event.maxListeners allows the limit to be modified for this specific event.
* The value can be set to 0 to indicate an unlimited number of listener.
*/
maxListeners: number
}
export namespace Event {
const _disposable = { dispose(): void { } };
export const None: Event<any> = Object.assign(function (): { dispose(): void } { return _disposable; }, {
get maxListeners(): number { return 0; },
set maxListeners(maxListeners: number) { }
});
/**
* Given an event and a `map` function, returns another event which maps each element
* through the mapping function.
*/
export function map<I, O>(event: Event<I>, mapFunc: (i: I) => O): Event<O> {
return Object.assign((listener: (e: O) => any, thisArgs?: any, disposables?: Disposable[]) => event(i => listener.call(thisArgs, mapFunc(i)), undefined, disposables), {
maxListeners: 0,
});
}
}
export type Callback = (...args: any[]) => any;
export class CallbackList implements Iterable<Callback> {
private _callbacks: Function[] | undefined;
private _contexts: any[] | undefined;
get length(): number {
return this._callbacks && this._callbacks.length || 0;
}
public add(callback: Function, context: any = undefined, bucket?: Disposable[]): void {
if (!this._callbacks) {
this._callbacks = [];
this._contexts = [];
}
this._callbacks.push(callback);
this._contexts!.push(context);
if (Array.isArray(bucket)) {
bucket.push({ dispose: () => this.remove(callback, context) });
}
}
public remove(callback: Function, context: any = undefined): void {
if (!this._callbacks) {
return;
}
let foundCallbackWithDifferentContext = false;
for (let i = 0; i < this._callbacks.length; i++) {
if (this._callbacks[i] === callback) {
if (this._contexts![i] === context) {
// callback & context match => remove it
this._callbacks.splice(i, 1);
this._contexts!.splice(i, 1);
return;
} else {
foundCallbackWithDifferentContext = true;
}
}
}
if (foundCallbackWithDifferentContext) {
throw new Error('When adding a listener with a context, you should remove it with the same context');
}
}
// tslint:disable-next-line:typedef
public [Symbol.iterator]() {
if (!this._callbacks) {
return [][Symbol.iterator]();
}
const callbacks = this._callbacks.slice(0);
const contexts = this._contexts!.slice(0);
return callbacks.map((callback, i) =>
(...args: any[]) => callback.apply(contexts[i], args)
)[Symbol.iterator]();
}
public invoke(...args: any[]): any[] {
const ret: any[] = [];
for (const callback of this) {
try {
ret.push(callback(...args));
} catch (e) {
console.error(e);
}
}
return ret;
}
public isEmpty(): boolean {
return !this._callbacks || this._callbacks.length === 0;
}
public dispose(): void {
this._callbacks = undefined;
this._contexts = undefined;
}
}