mini-signals
Version:
signals, in TypeScript, fast
132 lines (131 loc) • 3.69 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MiniSignal = void 0;
const MINI_SIGNAL_KEY = Symbol('SIGNAL');
function isMiniSignalNodeRef(obj) {
return typeof obj === 'object' && MINI_SIGNAL_KEY in obj;
}
class MiniSignal {
constructor() {
/**
* A Symbol that is used to guarantee the uniqueness of the MiniSignal
* instance.
*/
this._symbol = Symbol('MiniSignal');
this._refMap = new WeakMap();
this._head = undefined;
this._tail = undefined;
this._dispatching = false;
}
hasListeners() {
return this._head != null;
}
/**
* Dispatches a signal to all registered listeners.
*/
dispatch(...args) {
if (this._dispatching) {
throw new Error('MiniSignal#dispatch(): Signal already dispatching.');
}
let node = this._head;
if (node == null)
return false;
this._dispatching = true;
while (node != null) {
node.fn(...args);
node = node.next;
}
this._dispatching = false;
return true;
}
/**
* Register a new listener.
*/
add(fn) {
if (typeof fn !== 'function') {
throw new Error('MiniSignal#add(): First arg must be a Function.');
}
return this._createRef(this._addNode({ fn }));
}
/**
* Remove binding object.
*/
detach(sym) {
if (!isMiniSignalNodeRef(sym)) {
throw new Error('MiniSignal#detach(): First arg must be a MiniSignal listener reference.');
}
if (sym[MINI_SIGNAL_KEY] !== this._symbol) {
throw new Error('MiniSignal#detach(): MiniSignal listener does not belong to this MiniSignal.');
}
const node = this._refMap.get(sym);
if (!node)
return this; // already detached
this._refMap.delete(sym);
this._disconnectNode(node);
this._destroyNode(node);
return this;
}
/**
* Detach all listeners.
*/
detachAll() {
let n = this._head;
if (n == null)
return this;
this._head = this._tail = undefined;
this._refMap = new WeakMap();
while (n != null) {
this._destroyNode(n);
n = n.next;
}
return this;
}
_destroyNode(node) {
node.fn = undefined;
node.prev = undefined;
}
_disconnectNode(node) {
if (node === this._head) {
// first node
this._head = node.next;
if (node.next == null) {
this._tail = undefined;
}
}
else if (node === this._tail) {
// last node
this._tail = node.prev;
if (this._tail != null) {
this._tail.next = undefined;
}
}
if (node.prev != null) {
node.prev.next = node.next;
}
if (node.next != null) {
node.next.prev = node.prev;
}
}
_addNode(node) {
if (this._head == null) {
this._head = node;
this._tail = node;
}
else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this._tail.next = node;
node.prev = this._tail;
this._tail = node;
}
return node;
}
_createRef(node) {
const sym = { [MINI_SIGNAL_KEY]: this._symbol };
this._refMap.set(sym, node);
return sym;
}
_getRef(sym) {
return this._refMap.get(sym);
}
}
exports.MiniSignal = MiniSignal;