src/retry-job.tsTypeScript
@@ -0,0 +1,42 @@
1+
export class HttpError extends Error {2+
constructor(readonly status: number, message: string) {3+
super(message);4+
this.name = "HttpError";5+
}6+
}7+
8+
export interface RetryJobDependencies {9+
deliver(): Promise<void>;10+
sleep(milliseconds: number): Promise<void>;11+
random(): number;12+
markFailed(error: unknown): Promise<void>;13+
}14+
15+
export interface RetryPolicy {16+
maxAttempts: number;17+
baseDelayMs: number;18+
}19+
20+
export async function runRetryableJob(21+
policy: RetryPolicy,22+
dependencies: RetryJobDependencies,23+
): Promise<void> {24+
for (let attempt = 0; attempt <= policy.maxAttempts; attempt += 1) {25+
try {26+
await dependencies.deliver();27+
return;28+
} catch (error) {29+
if (error instanceof HttpError && error.status >= 400 && error.status < 500) {30+
await dependencies.markFailed(error);31+
return;32+
}33+
34+
if (attempt === policy.maxAttempts) {35+
await dependencies.markFailed(error);36+
return;37+
}38+
39+
await dependencies.sleep(policy.baseDelayMs * 2 ** attempt);40+
}41+
}42+
}