@glandjs/emitter
Version:
A fast, zero‑dependency event emitter for building scalable, event‑driven applications.
136 lines (135 loc) • 4.69 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventEmitter = void 0;
class EventEmitter {
constructor(spliter = ':', maxCacheSize = 6) {
this.spliter = spliter;
this.maxCacheSize = maxCacheSize;
this.tree = {};
this.cache = {};
this.id = 0;
}
on(event, listener) {
let node = this.tree;
const parts = event.split(this.spliter);
for (let i = 0, len = parts.length; i < len; i++) {
const part = parts[i];
node[part] = node[part] || (i === len - 1 ? [] : {});
if (i === len - 1) {
node[part].push(listener);
}
else {
node = node[part];
}
}
this.cache = {};
return this;
}
off(event, listener) {
const parts = event.split(this.spliter);
let node = this.tree;
const stack = [{ node, key: parts[0] }];
let shouldCleanup = false;
for (let i = 0, len = parts.length; i < len; i++) {
const part = parts[i];
if (!node[part])
return this;
if (i === len - 1) {
if (!listener) {
delete node[part];
shouldCleanup = true;
}
else {
const listeners = node[part];
const index = listeners.indexOf(listener);
if (index >= 0) {
listeners.splice(index, 1);
if (!listeners.length) {
delete node[part];
shouldCleanup = true;
}
}
}
}
else {
node = node[part];
if (i < len - 1)
stack.push({ node, key: parts[i + 1] });
}
}
if (shouldCleanup) {
for (let i = stack.length - 1; i >= 0; i--) {
const item = stack[i];
if (!item)
continue;
const { node, key } = item;
if (node[key] && Object.keys(node[key]).length === 0) {
delete node[key];
}
}
}
this.cache = {};
return this;
}
emit(event, payload) {
const cached = this.cache[event];
if (cached) {
cached.timestamp = ++this.id;
for (let i = 0, len = cached.listeners.length; i < len; i++) {
cached.listeners[i](payload);
}
return this;
}
const listeners = [];
const parts = event.split(this.spliter);
this.findListeners(this.tree, parts, 0, listeners);
if (listeners.length) {
this.cache[event] = { listeners, timestamp: ++this.id };
const keys = Object.keys(this.cache);
if (keys.length > this.maxCacheSize) {
let oldestKey = keys[0];
let oldestTimestamp = this.cache[oldestKey].timestamp;
for (let i = 1, len = keys.length; i < len; i++) {
const key = keys[i];
if (this.cache[key].timestamp < oldestTimestamp) {
oldestKey = key;
oldestTimestamp = this.cache[key].timestamp;
}
}
delete this.cache[oldestKey];
}
for (let i = 0, len = listeners.length; i < len; i++) {
listeners[i](payload);
}
}
return this;
}
findListeners(node, parts, depth, listeners) {
if (depth === parts.length) {
if (Array.isArray(node)) {
listeners.push(...node);
}
return;
}
const part = parts[depth];
const nextNode = node[part];
if (nextNode) {
if (depth === parts.length - 1 && Array.isArray(nextNode)) {
listeners.push(...nextNode);
}
else if (typeof nextNode === 'object') {
this.findListeners(nextNode, parts, depth + 1, listeners);
}
}
const wildcardNode = node['*'];
if (wildcardNode) {
if (depth === parts.length - 1 && Array.isArray(wildcardNode)) {
listeners.push(...wildcardNode);
}
else if (typeof wildcardNode === 'object') {
this.findListeners(wildcardNode, parts, depth + 1, listeners);
}
}
}
}
exports.EventEmitter = EventEmitter;