private calculateDelay

This commit is contained in:
2026-08-10 02:28:06 +00:00
parent f06ac63d0d
commit ce3d79181e
2 changed files with 34 additions and 59 deletions
+34 -34
View File
@@ -135,33 +135,6 @@ export class ExponentialBackoff {
return backoff.run(taskFn, onError); 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 * 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. // 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 nextAttemptCount = attempt + 1;
const nextAttemptExceedsMaxAttempts = nextAttemptCount >= this.#options.maxAttempts; const nextAttemptExceedsMaxAttempts = nextAttemptCount >= this.#options.maxAttempts;
@@ -282,14 +261,8 @@ export class ExponentialBackoff {
break; 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 // 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)); await new Promise((resolve) => setTimeout(resolve, delay));
attempt++; attempt++;
@@ -298,4 +271,31 @@ export class ExponentialBackoff {
// We completed the loop without ever succeeding. Throw an ExponentialBackoffMaxRetriesHitError with all the errors we got // We completed the loop without ever succeeding. Throw an ExponentialBackoffMaxRetriesHitError with all the errors we got
throw new ExponentialBackoffMaxRetriesHitError(errors); 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;
}
} }
-25
View File
@@ -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 */ /** Tests that passing undefined into the constructor does not cause an error during spread */
const testExponentialBackoffConstructorDoesNotCauseErrorDuringSpread = async (): Promise<void> => { const testExponentialBackoffConstructorDoesNotCauseErrorDuringSpread = async (): Promise<void> => {
@@ -649,10 +628,6 @@ const runTests = async (): Promise<void> => {
test('ExponentialBackoff.validateOptions: rejects Infinity', testExponentialBackoffValidateOptionsRejectsNonFiniteValues); test('ExponentialBackoff.validateOptions: rejects Infinity', testExponentialBackoffValidateOptionsRejectsNonFiniteValues);
test('ExponentialBackoff.validateOptions: rejects non-integer values', testExponentialBackoffValidateOptionsRejectsNonIntegerValues); test('ExponentialBackoff.validateOptions: rejects non-integer values', testExponentialBackoffValidateOptionsRejectsNonIntegerValues);
test('ExponentialBackoff.validateOptions: rejects NaN', testExponentialBackoffValidateOptionsRejectsNaN); 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); test('ExponentialBackoff: constructor does not cause an error during spread', testExponentialBackoffConstructorDoesNotCauseErrorDuringSpread);
}; };