@uupaa/messagepassing
Version:
Implementation of lightweight message passing logic for TypeScript.
123 lines (109 loc) • 4.08 kB
text/typescript
export type Selector = string|number;
export interface MessageSubscriber {
onmessage(selector:Selector, options:any):any|void,
}
export type MessageOptions = number|string|undefined|object|Array<number>|Array<string>;
export class MessageResult {
private _result:Map<MessageSubscriber, any> = new Map();
set(subscriber:MessageSubscriber, value:any):void {
this._result.set(subscriber, value);
}
get(subscriber:MessageSubscriber):any {
return this._result.get(subscriber);
}
has(subscriber:MessageSubscriber):any {
return this._result.has(subscriber);
}
list():Array<any> {
return Array.from(this._result.values());
}
}
export class MessagePassing {
private _subscribers:Set<MessageSubscriber> = new Set();
private _selectors:Map<MessageSubscriber, Array<Selector>> = new Map();
to(...subscribers:Array<MessageSubscriber>):Message {
const to = new Set(subscribers.length ? subscribers.concat() // broadcast
: this._subscribers.keys()); // unicast, multicast
return new Message(this._selectors, to);
}
register(subscriber:MessageSubscriber, selectors:Array<Selector> = ["ping"]):MessagePassing {
if (!subscriber) {
throw new TypeError(`Invalid subscriber: ${subscriber}`);
}
if (!Array.isArray(selectors)) {
throw new TypeError(`Invalid selectors: ${selectors}`);
}
if (typeof subscriber.onmessage !== "function") {
throw new TypeError(`${subscriber.constructor.name} has not onmessage function`);
}
if (selectors.filter(selector => !selector).length) {
throw new TypeError(`Invalid selectors: selectors has null, from ${subscriber.constructor.name}`);
}
this._subscribers.add(subscriber);
this._selectors.set(subscriber, selectors.slice()); // shallow copy
return this;
}
unregister(subscriber:MessageSubscriber):MessagePassing {
this._subscribers.delete(subscriber);
this._selectors.delete(subscriber);
return this;
}
unregisterAll():MessagePassing {
this._subscribers.clear();
this._selectors.clear();
return this;
}
}
export class Message {
private _selectors:Map<MessageSubscriber, Array<Selector>>;
private _to:Set<MessageSubscriber>; // unique array
constructor(selectors:Map<MessageSubscriber, Array<Selector>>, to:Set<MessageSubscriber>) {
this._selectors = selectors;
this._to = to;
}
remove(subscriber:MessageSubscriber):Message {
this._to.delete(subscriber)
return this;
}
send(selector:Selector, options:MessageOptions = undefined):MessageResult {
const to = Array.from(this._to.values());
const result = new MessageResult();
try {
for (let i = 0, iz = to.length; i < iz; ++i) {
const subscriber:MessageSubscriber = to[i];
if (subscriber) {
const selectors = this._selectors.get(subscriber); // [ "ping", ... ]
if (selectors && selectors.includes(selector) ) {
const r:any = subscriber.onmessage(selector, options);
result.set(subscriber, r);
} else {
//console.warn(`Selector (${selector}) not allowed by the subscriber (${subscriber.constructor.name}) have been ignored.`);
}
}
}
} catch (err) {
console.error(err);
}
return result;
}
post(selector:Selector, options:MessageOptions = undefined):void {
const to = Array.from(this._to.values());
try {
setTimeout(() => {
for (let i = 0, iz = to.length; i < iz; ++i) {
const subscriber:MessageSubscriber = to[i];
if (subscriber) {
const selectors = this._selectors.get(subscriber); // [ "ping", ... ]
if (selectors && selectors.includes(selector) ) {
subscriber.onmessage(selector, options);
} else {
//console.warn(`Selector (${selector}) not allowed by the subscriber (${subscriber.constructor.name}) have been ignored.`);
}
}
}
}, 0);
} catch (err) {
console.error(err);
}
}
}