| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- /**
- * RefreshTimer - A utility for executing a callback with configurable retry behavior
- *
- * This timer executes a callback function and schedules the next execution based on the result:
- * - If the callback succeeds (returns true), it schedules the next attempt after a fixed interval
- * - If the callback fails (returns false), it uses exponential backoff up to a maximum interval
- */
- /**
- * Configuration options for the RefreshTimer
- */
- export interface RefreshTimerOptions {
- /**
- * The callback function to execute
- * Should return a Promise that resolves to a boolean indicating success (true) or failure (false)
- */
- callback: () => Promise<boolean>
- /**
- * Time in milliseconds to wait before next attempt after success
- * @default 50000 (50 seconds)
- */
- successInterval?: number
- /**
- * Initial backoff time in milliseconds for the first failure
- * @default 1000 (1 second)
- */
- initialBackoffMs?: number
- /**
- * Maximum backoff time in milliseconds
- * @default 300000 (5 minutes)
- */
- maxBackoffMs?: number
- }
- /**
- * A timer utility that executes a callback with configurable retry behavior
- */
- export class RefreshTimer {
- private callback: () => Promise<boolean>
- private successInterval: number
- private initialBackoffMs: number
- private maxBackoffMs: number
- private currentBackoffMs: number
- private attemptCount: number
- private timerId: NodeJS.Timeout | null
- private isRunning: boolean
- /**
- * Creates a new RefreshTimer
- *
- * @param options Configuration options for the timer
- */
- constructor(options: RefreshTimerOptions) {
- this.callback = options.callback
- this.successInterval = options.successInterval ?? 50000 // 50 seconds
- this.initialBackoffMs = options.initialBackoffMs ?? 1000 // 1 second
- this.maxBackoffMs = options.maxBackoffMs ?? 300000 // 5 minutes
- this.currentBackoffMs = this.initialBackoffMs
- this.attemptCount = 0
- this.timerId = null
- this.isRunning = false
- }
- /**
- * Starts the timer and executes the callback immediately
- */
- public start(): void {
- if (this.isRunning) {
- return
- }
- this.isRunning = true
- // Execute the callback immediately
- this.executeCallback()
- }
- /**
- * Stops the timer and cancels any pending execution
- */
- public stop(): void {
- if (!this.isRunning) {
- return
- }
- if (this.timerId) {
- clearTimeout(this.timerId)
- this.timerId = null
- }
- this.isRunning = false
- }
- /**
- * Resets the backoff state and attempt count
- * Does not affect whether the timer is running
- */
- public reset(): void {
- this.currentBackoffMs = this.initialBackoffMs
- this.attemptCount = 0
- }
- /**
- * Schedules the next attempt based on the success/failure of the current attempt
- *
- * @param wasSuccessful Whether the current attempt was successful
- */
- private scheduleNextAttempt(wasSuccessful: boolean): void {
- if (!this.isRunning) {
- return
- }
- if (wasSuccessful) {
- // Reset backoff on success
- this.currentBackoffMs = this.initialBackoffMs
- this.attemptCount = 0
- this.timerId = setTimeout(() => this.executeCallback(), this.successInterval)
- } else {
- // Increment attempt count
- this.attemptCount++
- // Calculate backoff time with exponential increase
- // Formula: initialBackoff * 2^(attemptCount - 1)
- this.currentBackoffMs = Math.min(
- this.initialBackoffMs * Math.pow(2, this.attemptCount - 1),
- this.maxBackoffMs,
- )
- this.timerId = setTimeout(() => this.executeCallback(), this.currentBackoffMs)
- }
- }
- /**
- * Executes the callback and handles the result
- */
- private async executeCallback(): Promise<void> {
- if (!this.isRunning) {
- return
- }
- try {
- const result = await this.callback()
- this.scheduleNextAttempt(result)
- } catch (_error) {
- // Treat errors as failed attempts
- this.scheduleNextAttempt(false)
- }
- }
- }
|