UNPKG

@freemework/common

Version:

Common library of the Freemework Project.

104 lines (94 loc) 3.39 kB
import { FLimit } from "../f_limit.js"; import { FLimitException } from "../f_limit_exception.js"; import { FInternalLimitSyncBase } from "./f_internal_limit_sync_base.js"; import { FInternalLimitTokenDeferred } from "./f_internal_limit_token_deferred.js"; type FLimitTokenDeferred = FInternalLimitTokenDeferred & { finalize: () => void, finalizing: boolean }; export class FLimitInternalTimeSpanLimit extends FInternalLimitSyncBase { private readonly _maxTokens: number; private _activeTokenDefers: Array<FLimitTokenDeferred>; private readonly _delay: number; private readonly _clearTimeoutFunc: (handle?: number) => void; private readonly _setTimeoutFunc: (handler: Function, timeout?: number) => number; private readonly _timers: Array<number> = []; public constructor(delay: number, hitCount: number, stubs: { clearTimeoutFunc: (handle?: number) => void, setTimeoutFunc: (handler: Function, timeout?: number) => number } = { clearTimeoutFunc: (...args) => clearTimeout(...args), setTimeoutFunc: (...args) => setTimeout(...args) } ) { super(); this._maxTokens = hitCount; this._delay = delay; this._clearTimeoutFunc = stubs.clearTimeoutFunc; this._setTimeoutFunc = stubs.setTimeoutFunc; this._activeTokenDefers = []; } public get availableWeight(): number { if (super.disposed) { throw new Error("Wrong operation on disposed object"); } return this._maxTokens - this._activeTokenDefers.reduce((p, c) => p + c.weight, 0); } public get maxWeight(): number { if (super.disposed) { throw new Error("Wrong operation on disposed object"); } return this._maxTokens; } public accrueToken(weight: FLimit.Weight): FLimit.Token { super.verifyNotDisposed(); if (this.availableWeight < weight) { throw new FLimitException("No any available tokens"); } let defer: FLimitTokenDeferred | null = null; { // local scope const realDefer: FLimitTokenDeferred = { ...FInternalLimitTokenDeferred.create<void>(weight), finalize: () => { realDefer.resolve(); const index = this._activeTokenDefers.indexOf(realDefer); this._activeTokenDefers.splice(index, 1); this.raiseReleaseToken(); }, finalizing: false }; this._activeTokenDefers.push(realDefer); defer = realDefer; } const token: FLimit.Token = { commit: () => { if (defer !== null) { const selfDefer = defer; defer = null; selfDefer.finalizing = true; if (!super.disposing) { const timer = this._setTimeoutFunc(() => { const timerIndex = this._timers.indexOf(timer); if (timerIndex !== -1) { this._timers.splice(timerIndex, 1); } selfDefer.finalize(); }, this._delay); this._timers.push(timer); } else { selfDefer.finalize(); } } }, rollback: () => { if (defer !== null) { const selfDefer = defer; defer = null; selfDefer.finalizing = true; selfDefer.finalize(); } } }; return token as FLimit.Token; } protected async onDispose(): Promise<void> { this._timers.slice().forEach(timer => { this._clearTimeoutFunc(timer); const timerIndex = this._timers.indexOf(timer); if (timerIndex !== -1) { this._timers.splice(timerIndex, 1); } }); this._activeTokenDefers.filter(w => w.finalizing).forEach(d => d.finalize()); await Promise.all(this._activeTokenDefers.map(d => d.promise)); } }