From f07b65511e4774fb9713a6bff56f6456ca9f3cb3 Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Mon, 10 Aug 2026 02:48:39 +0000 Subject: [PATCH] Move calculateDelay back to private. Move abort signal check --- source/exponential-backoff.ts | 68 ++++++++++++++++---------------- test/exponential-backoff.test.ts | 25 ------------ 2 files changed, 34 insertions(+), 59 deletions(-) diff --git a/source/exponential-backoff.ts b/source/exponential-backoff.ts index f359f8c..be17f59 100644 --- a/source/exponential-backoff.ts +++ b/source/exponential-backoff.ts @@ -135,33 +135,6 @@ export class ExponentialBackoff { return backoff.run(taskFn, onError); } - /** - * Calculate the delay before we should attempt to retry - * - * @param options - The configuration for the exponential backoff - * @param attempt - The current attempt number - * @returns The time in milliseconds before another attempt should be made - */ - public static calculateDelay(options: ExponentialBackoffOptions, attempt: number): number { - // Get the power of the growth rate - const power = options.growthRate ** attempt; - - // Get the delay before jitter or limit - const rawDelay = options.baseDelay * power; - - // Cap the delay to the maximum. Do this before the jitter so jitter does not become larger than delay - const cappedDelay = Math.min(rawDelay, options.maxDelay); - - // Get a random number for the amount to "jitter" the delay by - const jitterAmount = Math.random(); - - // Calculate the jitter - const jitter = jitterAmount * options.jitter * cappedDelay; - - // Subtract the jitter from the delay - return cappedDelay - jitter; - } - /** * Validate the options for the exponential backoff * @@ -273,6 +246,12 @@ export class ExponentialBackoff { } } + // Check if the abort signal has been activated + if (abortController.signal.aborted) { + // Throw an error if the abort signal has been activated + throw new ExponentialBackoffStoppedRetriesError(abortController.signal.reason); + } + // Calculate the count for next attempt. Do this now so we can exit before waiting and before running the next attempt. const nextAttemptCount = attempt + 1; const nextAttemptExceedsMaxAttempts = nextAttemptCount >= this.#options.maxAttempts; @@ -282,14 +261,8 @@ export class ExponentialBackoff { break; } - // Check if the abort signal has been activated - if (abortController.signal.aborted) { - // Throw an error if the abort signal has been activated - throw new ExponentialBackoffStoppedRetriesError(abortController.signal.reason); - } - // Wait before going to the next attempt - const delay = ExponentialBackoff.calculateDelay(this.#options, attempt); + const delay = this.#calculateDelay(this.#options, attempt); await new Promise((resolve) => setTimeout(resolve, delay)); attempt++; @@ -298,4 +271,31 @@ export class ExponentialBackoff { // We completed the loop without ever succeeding. Throw an ExponentialBackoffMaxRetriesHitError with all the errors we got throw new ExponentialBackoffMaxRetriesHitError(errors); } + + /** + * Calculate the delay before we should attempt to retry + * + * @param options - The configuration for the exponential backoff + * @param attempt - The current attempt number + * @returns The time in milliseconds before another attempt should be made + */ + #calculateDelay(options: ExponentialBackoffOptions, attempt: number): number { + // Get the power of the growth rate + const power = options.growthRate ** attempt; + + // Get the delay before jitter or limit + const rawDelay = options.baseDelay * power; + + // Cap the delay to the maximum. Do this before the jitter so jitter does not become larger than delay + const cappedDelay = Math.min(rawDelay, options.maxDelay); + + // Get a random number for the amount to "jitter" the delay by + const jitterAmount = Math.random(); + + // Calculate the jitter + const jitter = jitterAmount * options.jitter * cappedDelay; + + // Subtract the jitter from the delay + return cappedDelay - jitter; + } } diff --git a/test/exponential-backoff.test.ts b/test/exponential-backoff.test.ts index a27b374..2eda021 100644 --- a/test/exponential-backoff.test.ts +++ b/test/exponential-backoff.test.ts @@ -586,27 +586,6 @@ const testExponentialBackoffValidateOptionsRejectsNaN = (): void => { } }; -/** Tests that calculateDelay will not result in NaN from extremely large growth rates and attempts */ -const testExponentialBackoffCalculateDelayDoesNotResultInNaN = (): void => { - // Large number, 1 trillion. - // Theory being that 1 trillion to the power of 1 trillion should be a very large number and cause either an unsafe value or a NaN. - const largeNumber = 1_000_000_000_000; - - // Test the calculateDelay function - const result = ExponentialBackoff.calculateDelay( - { - baseDelay: 10000, - growthRate: largeNumber, - jitter: 0, - maxDelay: 10_000, - maxAttempts: largeNumber, - }, - largeNumber, - ); - - // Test to ensure it was bounded to the max delay - expect(result).toBe(10_000); -}; /** Tests that passing undefined into the constructor does not cause an error during spread */ const testExponentialBackoffConstructorDoesNotCauseErrorDuringSpread = async (): Promise => { @@ -649,10 +628,6 @@ const runTests = async (): Promise => { test('ExponentialBackoff.validateOptions: rejects Infinity', testExponentialBackoffValidateOptionsRejectsNonFiniteValues); test('ExponentialBackoff.validateOptions: rejects non-integer values', testExponentialBackoffValidateOptionsRejectsNonIntegerValues); test('ExponentialBackoff.validateOptions: rejects NaN', testExponentialBackoffValidateOptionsRejectsNaN); - test( - 'ExponentialBackoff: calculateDelay does not result in NaN from extremely large growth rates and attempts', - testExponentialBackoffCalculateDelayDoesNotResultInNaN, - ); test('ExponentialBackoff: constructor does not cause an error during spread', testExponentialBackoffConstructorDoesNotCauseErrorDuringSpread); };