dash-core
Version:
A foundational toolkit of types, collections, services, and architectural patterns designed to accelerate application development.
143 lines (110 loc) • 5.37 kB
text/typescript
import { TimeSpan, ServiceLogger } from 'dash-core';
type pollerOptions = {
/** Initial delay before starting the first poll cycle. */
initialDelay: TimeSpan,
/** Maximum delay between polling cycles. */
maxDelay: TimeSpan,
/** Factor by which the delay will be multiplied on each failure. */
factor: number,
/** Time period after which the delay will reset to the initial delay if no errors occur. */
resetPeriod: TimeSpan,
/** The step used for linear delay decrease. */
linearStep: TimeSpan,
/** Optional logger for logging events during polling. */
logger?: ServiceLogger,
}
/**
* Handles polling with adaptive intervals, adjusting delays based on success or failure.
*/
export class AdaptivePoller {
private currentTimeout?: NodeJS.Timeout;
private currentDelay: number;
private lastErrorTime?: number;
private polling: boolean = false;
private operation?: (() => Promise<void>);
private onError?: ((error: Error) => void);
private onFail?: ((error: Error) => void);
private readonly options: pollerOptions;
private readonly defaultOptions: pollerOptions = {
initialDelay: TimeSpan.fromSeconds(1),
maxDelay: TimeSpan.fromMinutes(1),
factor: 1.5,
resetPeriod: TimeSpan.fromMinutes(5),
linearStep: TimeSpan.fromSeconds(1)
}
/**
* Returns whether the polling operation is currently active.
* @returns {boolean} True if polling is ongoing, false otherwise.
*/
public get isPolling(): boolean {
return this.polling;
}
/**
* Creates an instance of the AdaptivePoller class.
* @param {pollerOptions} options - Configuration options for the polling mechanism.
* @param {TimeSpan} options.initialDelay - The initial delay before the first polling attempt.
* @param {TimeSpan} options.maxDelay - The maximum delay between polling attempts.
* @param {number} options.factor - The factor by which the delay will be multiplied after each failure.
* @param {TimeSpan} options.resetPeriod - The period after which the delay will reset to the initial value if no errors occur.
* @param {TimeSpan} options.linearStep - The step used to decrease the delay linearly on success.
* @param {ServiceLogger} [options.logger] - Optional logger to log events during polling.
*/
constructor(options?: Partial<pollerOptions>) {
this.options = { ...this.defaultOptions, ...options };
this.currentDelay = this.options.initialDelay.totalMilliseconds;
}
/**
* Starts the polling process, which continues until explicitly stopped.
* @param {() => Promise<void>} operation - The operation to perform during each poll cycle.
* @param {(error: Error) => void} onError - Optional callback to handle errors during polling.
* @param {(error: Error) => void} onFail - Optional callback to handle failure events (when the interval exceeds maxDelay).
*/
public start(operation: () => Promise<void>, onError?: (error: Error) => void, onFail?: (error: Error) => void) {
if (this.polling) return;
this.polling = true;
this.operation = operation;
this.onError = onError;
this.onFail = onFail;
this.currentDelay = this.options.initialDelay.totalMilliseconds;
this.runPoll();
this.options.logger?.info('AdaptivePolling started');
}
/**
* Stops the polling process.
*/
public stop(): void {
this.polling = false;
clearTimeout(this.currentTimeout);
this.options.logger?.info('AdaptivePolling stopped');
}
private async runPoll(): Promise<void> {
if (!this.polling || !this.operation) return;
try {
await this.operation();
const now = Date.now();
// Reset the delay if no error occurred within the reset period
if (!this.lastErrorTime || (now - this.lastErrorTime) >= this.options.resetPeriod.totalMilliseconds) {
const initialDelay = this.options.initialDelay.totalMilliseconds;
const linearDifference = this.currentDelay - this.options.linearStep.totalMilliseconds;
this.currentDelay = Math.max(initialDelay, linearDifference);
}
} catch (error) {
this.lastErrorTime = Date.now();
this.currentDelay = this.currentDelay * this.options.factor;
const resultError = error instanceof Error ? error : new Error(JSON.stringify(error));
if (this.currentDelay >= this.options.maxDelay.totalMilliseconds) {
// If delay exceeds maxDelay, trigger the onFail callback
this.currentDelay = this.options.maxDelay.totalMilliseconds;
this.onFail?.(resultError);
} else {
// If an error occurred but delay is still within max, trigger the onError callback
this.onError?.(resultError);
}
} finally {
this.scheduleNextPoll();
}
}
private scheduleNextPoll(): void {
this.currentTimeout = setTimeout(() => this.runPoll(), this.currentDelay);
}
}