This commit refactors existing text utility functions into the application layer for broad reuse and integrates them across the codebase. Initially, these utilities were confined to test code, which limited their application. Changes: - Move text utilities to the application layer. - Centralize text utilities into dedicated files for better maintainability. - Improve robustness of utility functions with added type checks. - Replace duplicated logic with centralized utility functions throughout the codebase. - Expand unit tests to cover refactored code parts.
57 lines
2.0 KiB
TypeScript
57 lines
2.0 KiB
TypeScript
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
|
|
import { indentText } from '@/application/Common/Text/IndentText';
|
|
import { type UrlStatus, formatUrlStatus } from './UrlStatus';
|
|
|
|
const DefaultBaseRetryIntervalInMs = 5 /* sec */ * 1000;
|
|
|
|
export async function retryWithExponentialBackOff(
|
|
action: () => Promise<UrlStatus>,
|
|
baseRetryIntervalInMs: number = DefaultBaseRetryIntervalInMs,
|
|
currentRetry = 1,
|
|
): Promise<UrlStatus> {
|
|
const maxTries = 3;
|
|
const status = await action();
|
|
if (shouldRetry(status)) {
|
|
if (currentRetry <= maxTries) {
|
|
const exponentialBackOffInMs = getRetryTimeoutInMs(currentRetry, baseRetryIntervalInMs);
|
|
console.log([
|
|
`Attempt ${currentRetry}: Retrying in ${exponentialBackOffInMs / 1000} seconds.`,
|
|
'Details:',
|
|
indentText(formatUrlStatus(status)),
|
|
].join('\n'));
|
|
await sleep(exponentialBackOffInMs);
|
|
return retryWithExponentialBackOff(action, baseRetryIntervalInMs, currentRetry + 1);
|
|
}
|
|
console.warn('💀 All retry attempts failed. Final failure to retrieve URL:', indentText(formatUrlStatus(status)));
|
|
}
|
|
return status;
|
|
}
|
|
|
|
function shouldRetry(status: UrlStatus): boolean {
|
|
if (status.error) {
|
|
return true;
|
|
}
|
|
if (status.code === undefined) {
|
|
return true;
|
|
}
|
|
return isTransientError(status.code)
|
|
|| status.code === 429; // Too Many Requests
|
|
}
|
|
|
|
function isTransientError(statusCode: number): boolean {
|
|
return statusCode >= 500 && statusCode <= 599;
|
|
}
|
|
|
|
function getRetryTimeoutInMs(
|
|
currentRetry: number,
|
|
baseRetryIntervalInMs: number = DefaultBaseRetryIntervalInMs,
|
|
): number {
|
|
const retryRandomFactor = 0.5; // Retry intervals are between 50% and 150%
|
|
// of the exponentially increasing base amount
|
|
const minRandom = 1 - retryRandomFactor;
|
|
const maxRandom = 1 + retryRandomFactor;
|
|
const randomization = (Math.random() * (maxRandom - minRandom)) + maxRandom;
|
|
const exponential = 2 ** (currentRetry - 1);
|
|
return Math.ceil(exponential * baseRetryIntervalInMs * randomization);
|
|
}
|