@downpourdigital/dispatcher
Version:
Async event dispatcher.
118 lines (112 loc) • 3.76 kB
JavaScript
class Subscription {
constructor(observer, subscribable) {
this.observer = observer;
this.subscribable = subscribable;
}
unsubscribe() {
this.subscribable.unsubscribe(this);
}
}
class PassiveSubscription extends Subscription {
constructor(observer, subscribable) {
super((cb, payload) => {
cb();
observer(payload);
return null;
}, subscribable);
}
}
class Subscribable {
constructor() {
this.listeners = [];
}
subscribe(observer) {
const subscription = new Subscription(observer, this);
this.listeners.push(subscription);
return subscription;
}
subscribePassive(observer) {
const subscription = new PassiveSubscription(observer, this);
this.listeners.push(subscription);
return subscription;
}
unsubscribe(subscription) {
this.listeners.splice(this.listeners.findIndex(s => s === subscription), 1);
}
}
class PersistentEvent {
constructor(listeners, payload, mayCancelAfterCallback) {
this.canceled = false;
this.isAccumulating = true;
this.listeners = [];
this.completedListeners = [];
this.mayCancelAfterCallback = !!mayCancelAfterCallback;
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
listeners.forEach((s) => {
const ref = {
subscription: s,
cancel: null,
};
this.listeners.push(ref);
const cancel = s.observer(() => { this.completeCallback(s); }, payload) || null;
ref.cancel = cancel;
});
this.isAccumulating = false;
if (this.listeners.length === 0 && this.resolve)
this.resolve();
}
completeCallback(subscription) {
if (!this.canceled) {
const [listener] = this.listeners.splice(this.listeners.findIndex(l => l.subscription === subscription), 1);
if (this.mayCancelAfterCallback && listener) {
this.completedListeners.push(listener);
}
if (this.listeners.length === 0 && !this.isAccumulating && this.resolve) {
this.resolve();
}
}
}
cancel() {
if (!this.canceled) {
this.canceled = true;
this.listeners.forEach((listener) => {
if (listener.cancel)
listener.cancel();
});
if (this.mayCancelAfterCallback) {
this.completedListeners.forEach((listener) => {
if (listener.cancel)
listener.cancel();
});
}
if (this.reject)
this.reject();
}
}
}
class EventDispatcher extends Subscribable {
constructor(props = {}) {
super();
this.events = [];
const { mayCancelAfterCallback = true, } = props;
this.mayCancelAfterCallback = mayCancelAfterCallback;
}
dispatchPassive(payload) {
this.listeners.forEach(s => s.observer(() => { }, payload));
}
dispatch(payload) {
const event = new PersistentEvent(this.listeners, payload, this.mayCancelAfterCallback);
this.events.push(event);
event.promise.catch(() => { }).finally(() => {
this.events.splice(this.events.findIndex(e => e === event), 1);
});
return event;
}
cancelAll() {
this.events.forEach(e => e.cancel());
}
}
export { EventDispatcher, Subscribable };