Files
generalFunctions/src/domain/LatestWinsGate.js

75 lines
2.5 KiB
JavaScript
Raw Normal View History

'use strict';
// Serialises an async dispatch so that high-frequency callers cannot stack
// up overlapping invocations. Intermediate values are dropped — only the
// most recent fire() during an in-flight dispatch is replayed afterwards.
// Extracted from machineGroupControl's _dispatchInFlight + _delayedCall
// pattern so MGC, pumpingStation, valveGroupControl etc. can share it.
class LatestWinsGate {
constructor(asyncDispatchFn, options = {}) {
if (typeof asyncDispatchFn !== 'function') {
throw new TypeError('LatestWinsGate requires an async dispatch function');
}
this._dispatch = asyncDispatchFn;
this._logger = options.logger || null;
this._inFlight = false;
this._pending = null; // { value, ctx } | null
this._drainResolvers = []; // resolved when idle again
this.lastError = null;
}
// 0 = idle, 1 = running with no pending, 2 = running with pending.
get size() {
if (!this._inFlight) return 0;
return this._pending ? 2 : 1;
}
// Never blocks the caller. If a dispatch is in flight, the latest
// value is parked; older parked values are silently overwritten.
fire(value, ctx) {
if (this._inFlight) {
this._pending = { value, ctx };
return;
}
this._run(value, ctx);
}
drain() {
if (!this._inFlight && !this._pending) return Promise.resolve();
return new Promise((resolve) => { this._drainResolvers.push(resolve); });
}
_run(value, ctx) {
this._inFlight = true;
// Kick the dispatch on a microtask so fire() always returns
// synchronously, even if _dispatch resolves immediately.
Promise.resolve()
.then(() => this._dispatch(value, ctx))
.catch((err) => {
this.lastError = err;
if (this._logger && typeof this._logger.error === 'function') {
this._logger.error(err);
}
// Swallow: an error must not deadlock the gate.
})
.then(() => this._afterDispatch());
}
_afterDispatch() {
this._inFlight = false;
if (this._pending) {
const { value, ctx } = this._pending;
this._pending = null;
this._run(value, ctx);
return;
}
// Idle — release any drain() waiters.
const waiters = this._drainResolvers;
this._drainResolvers = [];
for (const r of waiters) r();
}
}
module.exports = LatestWinsGate;