|
| 1 | +/** |
| 2 | +* This is a type for callbacks, passed into "Channel". Note that it has an "id" property for O(1) lookup when removing items. Please, don't touch it. |
| 3 | +* */ |
| 4 | +export type ChannelCB<T> = ((data: T)=>void) & {id: number} |
| 5 | + |
| 6 | +/** |
| 7 | +* An event channel, pub/sub pattern, replacement (for ordinary event emitter. Creation is faster, removal is O(1) (callbacks get "id" property for this, don't touch it), hence is scalable. When has one listener (not when it had more and then left 1) - has optimization. It is used in "HttpResponse.abortCh |
| 8 | +* If you want to have something like "emitter.emit("event", data)" just create an object with channels: {"event": new Channel()}. |
| 9 | +* It doesn't support "once" events, because all you need is just "clear" the channel for it. |
| 10 | +* It doesn't handle cases when you are deleting a listener within "pub" call. It can skip other listener. If you want both 'once' and 'on' listeners, create 2 separate channels for both handlers, for 'once' use "channel.clear()" |
| 11 | +* Took some inspiration from tseep. |
| 12 | +* @example |
| 13 | +* // any message you'd like to send. It can be anything |
| 14 | +* type MessageT = `hello, ${string}` |
| 15 | +* var channel = new Channel<MessageT>() |
| 16 | +* save function, but not anonymously (if you want then to remove it alone) |
| 17 | +* function subscriber1 (message: MessageT) { |
| 18 | +* console.log("Here is a message", message) |
| 19 | +* } |
| 20 | +* channel.sub(subscriber1) |
| 21 | +* function sub2 () {} |
| 22 | +* channel.sub(sub2) |
| 23 | +* // message is of MessageT |
| 24 | +* channel.pub(`hello, MISTER ANDERSON`) |
| 25 | +* // remove subscribers individually |
| 26 | +* channel.unsub(subscriber1) |
| 27 | +* // remove all subscribers at once |
| 28 | +* channel.clear() |
| 29 | +* */ |
| 30 | +export class Channel<MessageType> { |
| 31 | + protected cbs: ChannelCB<MessageType>[] = []; |
| 32 | + /**find out if there are any active listeners*/ |
| 33 | + get isEmpty() { return !this.cbs } |
| 34 | + /**subscribe to channel.*/ |
| 35 | + sub(fn: (msg: MessageType) => void) { |
| 36 | + var cbs = this.cbs; |
| 37 | + (fn as any).id = cbs.length; cbs.push(fn as any); |
| 38 | + } |
| 39 | + /**unsubscribe from channel - remove only callbacks, that are definitely stored inside. Otherwise function throws an error*/ |
| 40 | + unsub(fn: (msg: MessageType) => void) { |
| 41 | + var cbs = this.cbs |
| 42 | + let id: number = (fn as any).id |
| 43 | + if (id == cbs.length - 1) |
| 44 | + cbs.pop(); |
| 45 | + else { |
| 46 | + let newCb = (cbs[id] = cbs.pop()!); newCb.id = id; |
| 47 | + } |
| 48 | + } |
| 49 | + /**publish a message to the whole channel. While the function is active, don't remove any of channel's listeners. */ |
| 50 | + pub(data: MessageType){ |
| 51 | + var cbs = this.cbs |
| 52 | + for (let i = 0; i < cbs.length; i++) { |
| 53 | + cbs[i]!(data); |
| 54 | + } |
| 55 | + } |
| 56 | + clear() { this.cbs = [] } |
| 57 | +} |
| 58 | + |
| 59 | +/** |
| 60 | + * @description it is not written to be actively used. If you use "Channel" you eliminate unwanted overhead of using "too universal" tool like this EventEmitter. Its main purpose is to combine 'on' and 'once' listeners. |
| 61 | + **/ |
| 62 | +export class EventEmitter<T extends Record<string|number|symbol, any>> { |
| 63 | + /**All events that you use. If you need both 'once' and 'on' but want to avoid built-in methods of 'EventEmitter', you can use 'events' manually.*/ |
| 64 | + events: { |
| 65 | + [K in keyof T]: { |
| 66 | + on: Channel<T[K]>, |
| 67 | + once: Channel<T[K]> |
| 68 | + } | undefined |
| 69 | + } = {} as any |
| 70 | + on<K extends keyof T>(ev: K, handler: (this: Channel<T[K]>, data: T[K])=>void) { |
| 71 | + var obj = this.events[ev] |
| 72 | + if (!obj) this.events[ev] = obj = { |
| 73 | + on: new Channel(), |
| 74 | + once: new Channel() |
| 75 | + } |
| 76 | + obj.on.sub(handler) |
| 77 | + } |
| 78 | + once<K extends keyof T>(ev: K, handler: (this: Channel<T[K]>, data: T[K])=>void) { |
| 79 | + var obj = this.events[ev] |
| 80 | + if (!obj) this.events[ev] = obj = { |
| 81 | + on: new Channel(), |
| 82 | + once: new Channel() |
| 83 | + } |
| 84 | + obj.once.sub(handler) |
| 85 | + } |
| 86 | + /**Remove listener from event. For 'once' listeners you 'offOnce'*/ |
| 87 | + off<K extends keyof T>(ev: K, handler: (this: Channel<T[K]>, data: T[K])=>void) { |
| 88 | + var obj = this.events[ev] |
| 89 | + if(!obj || !("id" in handler)) return; |
| 90 | + obj.on.unsub(handler) |
| 91 | + delete handler.id |
| 92 | + } |
| 93 | + /**Remove 'once' listener from event*/ |
| 94 | + offOnce<K extends keyof T>(ev: K, handler: (this: Channel<T[K]>, data: T[K])=>void) { |
| 95 | + var obj = this.events[ev] |
| 96 | + if(!obj || !("id" in handler)) return; |
| 97 | + obj.once.unsub(handler) |
| 98 | + delete handler.id |
| 99 | + } |
| 100 | + /**Here message first goes to "once" listeners, then - 'on'*/ |
| 101 | + emit<K extends keyof T>(ev: K, data: T[K]) { |
| 102 | + var obj = this.events[ev] |
| 103 | + if(!obj) return; |
| 104 | + obj.once.pub(data); |
| 105 | + obj.once.clear(); |
| 106 | + obj.on.pub(data); |
| 107 | + } |
| 108 | + /**remove all listeners from specific event OR, if unspecificed, remove all events*/ |
| 109 | + removeAllListeners(ev?: keyof T) { |
| 110 | + ev ? delete this.events[ev] : this.events = {} as any |
| 111 | + } |
| 112 | +} |
0 commit comments