Skip to main content

retry()

retry<T>(fn, options): Promise<T>

Retries an async function with configurable backoff and error filtering.

WHY WRAPPING NATIVE?

We prefer to wrap this to improve resilience against transient failures, adhering to Fail Fast principles by managing retries declaratively. See Design Philosophy


Type Parametersโ€‹

T: Tโ€‹

The return type of the function.


Parametersโ€‹

fn: () => Promise<T>โ€‹

The async function to retry.

options: RetryOptions = {}โ€‹

Retry configuration options.


Returns: Promise<T>โ€‹

Promise that resolves to the function's return value.


Throwsโ€‹

The last error thrown by fn if all retries fail.


Sinceโ€‹

1.1.0


Performanceโ€‹

Exponential backoff with maxDelay cap prevents excessive wait times. Jitter prevents thundering herd problem in distributed systems.


Also known asโ€‹

retry (Radashi, Effect, Modern Dash) ยท โŒ (Lodash, es-toolkit, Remeda, Ramda, Antfu)


Exampleโ€‹

const result = await retry(() => fetchData(), { attempts: 3 });

const result = await retry(() => apiCall(), {
attempts: 5,
delay: 1000,
backoff: 2,
jitter: 0.5,
until: (error) => error.code !== 'PERMANENT_ERROR'
});

How it works?โ€‹

Retries a failed async function with configurable backoff and jitter. Exponential backoff increases delay between attempts โ€” jitter adds randomness to prevent thundering herd.

Exponential Backoffโ€‹

Jitter Visualizationโ€‹

Jitter adds randomness to delay to avoid synchronized retries:

Until (Error Filter)โ€‹

The until option aborts retry if error is permanent:


Use Casesโ€‹

Handle network failures gracefully ๐Ÿ“Œโ€‹

Implement robust retry logic for network operations with exponential backoff. Essential for handling transient network issues and improving reliability.

// Retry API calls with exponential backoff
const userData = await retry(
async () => {
const response = await fetch("/api/users/123");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
},
{
attempts: 5,
delay: 1000,
backoff: 2,
maxDelay: 10000,
until: (error) => error.message.includes("HTTP 5"),
}
);

console.log("User data loaded:", userData);

Recover from temporary service unavailabilityโ€‹

Handle temporary service outages with intelligent retry mechanisms. Critical for maintaining service availability and user experience.

// Retry database operations
const result = await retry(
async () => {
return await database.query("SELECT * FROM users WHERE active = ?", [true]);
},
{
attempts: 3,
delay: 500,
backoff: 1.5,
until: (error) => error.code === "CONNECTION_TIMEOUT",
}
);

console.log(`Found ${result.length} active users`);

Implement file upload resilienceโ€‹

Retry file uploads with progressive delays for better success rates. Essential for handling large file uploads and network instability.

// Retry file upload with custom error handling
const uploadResult = await retry(
async () => {
const formData = new FormData();
formData.append("file", file);

const response = await fetch("/api/upload", {
method: "POST",
body: formData,
});

if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}

return response.json();
},
{
attempts: 4,
delay: 2000,
backoff: 2,
until: (error) => error.message.includes("Upload failed"),
}
);

console.log("File uploaded successfully:", uploadResult);

Manage rate-limited API callsโ€‹

Handle API rate limiting with intelligent retry strategies. Critical for working with external APIs that have rate limits.

// Retry with rate limit handling
const apiData = await retry(
async () => {
const response = await fetch("/external-api/data");

if (response.status === 429) {
throw new Error("RATE_LIMITED");
}

return response.json();
},
{
attempts: 3,
delay: 5000, // Wait 5 seconds for rate limit reset
backoff: 1,
until: (error) => error.message === "RATE_LIMITED",
}
);

console.log("API data retrieved:", apiData);

Ensure critical operations succeedโ€‹

Guarantee execution of critical operations with comprehensive retry logic. Essential for payment processing, data synchronization, and critical business operations.

// Retry critical payment processing
const paymentResult = await retry(
async () => {
return await processPayment({
amount: 100.0,
currency: "USD",
paymentMethod: "card",
});
},
{
attempts: 5,
delay: 1000,
backoff: 2,
maxDelay: 30000,
until: (error) => error.code !== "INVALID_CARD",
}
);

console.log("Payment processed:", paymentResult);

Multi-gateway payment failover for financial systemsโ€‹

Retry payment processing across multiple gateways for maximum success rate. Critical for fintech applications and e-commerce platforms handling payments.

const gateways = ["stripe", "paypal", "adyen"];
let currentGatewayIndex = 0;

const processPaymentWithFailover = await retry(
async () => {
const gateway = gateways[currentGatewayIndex];

const result = await paymentAPI.process({
gateway,
amount: 150.00,
currency: "EUR",
cardToken: encryptedToken,
});

if (!result.success && result.error === "GATEWAY_UNAVAILABLE") {
currentGatewayIndex = (currentGatewayIndex + 1) % gateways.length;
throw new Error("GATEWAY_DOWN");
}

return result;
},
{
attempts: gateways.length * 2,
delay: 500,
backoff: 1.5,
until: (error) => error.message === "GATEWAY_DOWN",
}
);

console.log("Payment completed via:", gateways[currentGatewayIndex]);

RetryOptionsโ€‹

Interface

Configuration options for retry behavior.


Sinceโ€‹

2.0.0


Propertiesโ€‹

attempts?: numberโ€‹

Number of retry attempts. Defaults to 3.

delay?: numberโ€‹

Initial delay between retries in milliseconds. Defaults to 1000.

backoff?: numberโ€‹

Backoff multiplier for delay. Defaults to 1 (no backoff).

maxDelay?: numberโ€‹

Maximum delay between retries in milliseconds. Defaults to 10000.

jitter?: numberโ€‹

Random jitter factor (0-1) to add variation to delay. Defaults to 0.

until()?: (error) => booleanโ€‹

Function to determine if error should trigger retry. Return false to abort.

error: unknown
Returns: boolean