Tiny and elegant HTTP client based on the Fetch API
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Configurable retry logic with exponential backoff, status code filtering, and Retry-After header support. Ky automatically retries failed requests based on configurable conditions.
Configure retry behavior with comprehensive options for different failure scenarios.
interface RetryOptions {
/** Maximum number of retry attempts */
limit?: number;
/** HTTP methods allowed to retry */
methods?: string[];
/** HTTP status codes that trigger retry */
statusCodes?: number[];
/** Status codes that respect Retry-After header */
afterStatusCodes?: number[];
/** Maximum wait time for Retry-After header */
maxRetryAfter?: number;
/** Upper limit for retry delay */
backoffLimit?: number;
/** Custom delay calculation function */
delay?: (attemptCount: number) => number;
}Default Values:
const defaultRetryOptions = {
limit: 2,
methods: ['get', 'put', 'head', 'delete', 'options', 'trace'],
statusCodes: [408, 413, 429, 500, 502, 503, 504],
afterStatusCodes: [413, 429, 503],
maxRetryAfter: undefined, // Uses timeout value
backoffLimit: Infinity,
delay: (attemptCount) => 0.3 * (2 ** (attemptCount - 1)) * 1000
};Usage Examples:
import ky from "ky";
// Simple retry limit
const simpleRetry = await ky.get("https://api.example.com/unstable", {
retry: 5 // Retry up to 5 times with default settings
}).json();
// Custom retry configuration
const customRetry = await ky.get("https://api.example.com/data", {
retry: {
limit: 3,
methods: ["get", "post"],
statusCodes: [408, 429, 500, 502, 503, 504],
delay: (attemptCount) => Math.min(1000 * attemptCount, 5000)
}
}).json();
// Aggressive retry for critical operations
const criticalClient = ky.create({
retry: {
limit: 10,
methods: ["get", "post", "put", "patch", "delete"],
statusCodes: [408, 413, 429, 500, 502, 503, 504],
backoffLimit: 30000, // Max 30 second delay
delay: (attemptCount) => {
// Exponential backoff with jitter
const baseDelay = 1000 * (2 ** (attemptCount - 1));
const jitter = Math.random() * 0.1 * baseDelay;
return baseDelay + jitter;
}
}
});Automatically respect server-provided retry timing through HTTP headers.
Usage Examples:
import ky from "ky";
// Respect Retry-After header for rate limiting
const rateLimitedClient = ky.create({
retry: {
limit: 5,
afterStatusCodes: [413, 429, 503], // Honor Retry-After for these statuses
maxRetryAfter: 60000 // Wait maximum 60 seconds
}
});
// The client will automatically wait for the time specified in:
// - Retry-After header (seconds or HTTP date)
// - RateLimit-Reset header (fallback)
// - X-RateLimit-Reset header (GitHub-style)
// - X-Rate-Limit-Reset header (Twitter-style)
const data = await rateLimitedClient.get("https://api.example.com/limited").json();
// Custom max retry delay
const customDelayClient = ky.create({
retry: {
afterStatusCodes: [429],
maxRetryAfter: 10000, // Never wait more than 10 seconds
}
});Configure retry behavior per HTTP method for different operation types.
Usage Examples:
import ky from "ky";
// Safe methods only (idempotent operations)
const safeRetryClient = ky.create({
retry: {
limit: 5,
methods: ["get", "head", "options"], // Only safe methods
statusCodes: [408, 429, 500, 502, 503, 504]
}
});
// Include PUT and DELETE (idempotent)
const idempotentClient = ky.create({
retry: {
limit: 3,
methods: ["get", "put", "delete", "head"],
statusCodes: [408, 500, 502, 503, 504]
}
});
// Retry POST for specific use cases (be careful!)
const postRetryClient = ky.create({
retry: {
limit: 2,
methods: ["get", "post"], // Only if POST is idempotent
statusCodes: [408, 500, 502, 503, 504]
}
});
// Different retry strategies per method
const hybridClient = ky.create({
prefixUrl: "https://api.example.com"
});
// GET requests with aggressive retry
const getData = (endpoint: string) => hybridClient.get(endpoint, {
retry: { limit: 5, methods: ["get"] }
});
// POST requests with conservative retry
const postData = (endpoint: string, data: any) => hybridClient.post(endpoint, {
json: data,
retry: { limit: 1, methods: ["post"], statusCodes: [408, 500] }
});Configure which HTTP status codes should trigger retry attempts.
Usage Examples:
import ky from "ky";
// Default retriable status codes
const defaultClient = ky.create({
retry: {
statusCodes: [
408, // Request Timeout
413, // Payload Too Large (with Retry-After)
429, // Too Many Requests (with Retry-After)
500, // Internal Server Error
502, // Bad Gateway
503, // Service Unavailable (with Retry-After)
504 // Gateway Timeout
]
}
});
// Custom status codes for specific APIs
const customStatusClient = ky.create({
retry: {
statusCodes: [
408, 429, 500, 502, 503, 504, // Standard codes
520, 521, 522, 523, 524 // Cloudflare-specific codes
]
}
});
// Minimal retry for fast-fail scenarios
const fastFailClient = ky.create({
retry: {
limit: 1,
statusCodes: [500, 502, 503], // Only server errors
delay: () => 100 // Very short delay
}
});
// Never retry client errors (4xx)
const noClientErrorRetry = ky.create({
retry: {
statusCodes: [408, 500, 502, 503, 504], // Excludes 413, 429
afterStatusCodes: [] // No Retry-After respect
}
});Implement custom delay calculations for different backoff strategies.
Usage Examples:
import ky from "ky";
// Linear backoff
const linearClient = ky.create({
retry: {
delay: (attemptCount) => attemptCount * 1000 // 1s, 2s, 3s, 4s...
}
});
// Exponential backoff with jitter
const jitterClient = ky.create({
retry: {
delay: (attemptCount) => {
const baseDelay = 1000 * (2 ** (attemptCount - 1));
const jitter = Math.random() * 0.1 * baseDelay;
return baseDelay + jitter;
},
backoffLimit: 10000 // Cap at 10 seconds
}
});
// Fibonacci backoff
const fibonacciClient = ky.create({
retry: {
delay: (attemptCount) => {
const fibonacci = (n: number): number => {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
};
return fibonacci(attemptCount) * 1000;
}
}
});
// Adaptive delay based on response time
let lastResponseTime = 1000;
const adaptiveClient = ky.create({
retry: {
delay: (attemptCount) => {
// Increase delay based on server response time
const baseDelay = Math.max(lastResponseTime * 2, 1000);
return baseDelay * attemptCount;
}
},
hooks: {
afterResponse: [
(request, options, response) => {
// Track response time (simplified)
lastResponseTime = Date.now() - (request as any).startTime || 1000;
}
]
}
});Understanding how retry interacts with timeout settings.
Usage Examples:
import ky from "ky";
// Total timeout includes all retry attempts
const timeoutClient = ky.create({
timeout: 30000, // 30 seconds total (including retries)
retry: {
limit: 3,
delay: (attemptCount) => 2000 * attemptCount // 2s, 4s, 6s
}
});
// Per-attempt timeout
const perAttemptClient = ky.create({
timeout: 5000, // 5 seconds per attempt
retry: {
limit: 5,
delay: (attemptCount) => 1000 * attemptCount
}
});
// Fast timeout, many retries
const fastRetryClient = ky.create({
timeout: 3000, // Fail fast per attempt
retry: {
limit: 10, // But try many times
delay: (attemptCount) => Math.min(500 * attemptCount, 5000)
}
});
// No retry on timeout
const noTimeoutRetryClient = ky.create({
timeout: 10000,
retry: {
limit: 3,
// Timeouts are never retried automatically
statusCodes: [500, 502, 503, 504] // Excludes timeout scenarios
}
});interface RetryOptions {
limit?: number;
methods?: string[];
statusCodes?: number[];
afterStatusCodes?: number[];
maxRetryAfter?: number;
backoffLimit?: number;
delay?: (attemptCount: number) => number;
}
// Retry can be configured as number (limit only) or full options
type RetryConfiguration = RetryOptions | number;