Async Error Classification Retry Policy
Classify async failures into retryable and fatal buckets, retrying only transient errors with backoff while surfacing permanent ones.
Code
interface RetryableError extends Error {
retryable?: boolean;
}
class PermanentError extends Error {
retryable = false;
}
function classify(err: unknown): RetryableError {
if (err instanceof PermanentError) return err;
const message = err instanceof Error ? err.message : String(err);
const transient =
/timeout|econnreset|socket hang up|503|429/i.test(message);
return Object.assign(
err instanceof Error ? err : new Error(message),
{ retryable: transient },
);
}
async function withRetry<T>(
fn: () => Promise<T>,
{ attempts = 3, baseMs = 200 } = {},
): Promise<T> {
let lastError: RetryableError;
for (let i = 1; i <= attempts; i++) {
try {
return await fn();
} catch (raw) {
lastError = classify(raw);
if (!lastError.retryable || i === attempts) {
throw lastError;
}
await new Promise((r) => setTimeout(r, baseMs * 2 ** (i - 1)));
}
}
throw lastError!;
}
const result = await withRetry(async () => {
if (Math.random() < 0.7) {
const err = new Error('upstream 503 unavailable');
throw err;
}
return 'settled';
});
console.log('outcome:', result);Report a problem with this exampleOpens a pre-filled issue on GitHub.