advanced-retry v1.2.1
Advanced-Retry
A powerful and flexible retry library for TypeScript/JavaScript with support for custom retry strategies, error filtering, abort signals, and so much more.
Features
🎯 Type-safe API with focus on Developer Experience and reliable tests
🔄 Flexible retry strategies with defaults
🎉 Custom error resolvers, with full type safety and support for async/await
📊 Context passing between retry attempts
🎨 Powerful error filtering system, with support for status code, keyword and custom filters
⏱️ Timeout and abort signal support
📈 Retry statistics
🔍 Multiple operation handling
Install
npm install advanced-retry
Basic Usage
import { advancedRetry, delayErrorResolver } from 'advanced-retry';
// Simple retry with linear backoff
const result = await advancedRetry({
operation: async () => {
const response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error('API request failed');
return response.json();
},
errorResolvers: [
delayErrorResolver({
configuration: {
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 5000,
backoffMultiplier: 2,
},
}),
],
});
if (result.success) {
console.log('Data:', result.result);
console.log('Attempts needed:', result.totalAttemptsToSucceed);
} else {
console.error('Failed:', result.error);
}
Advanced Usage
Multiple Parallel Error Resolvers
const result = await advancedRetry({
operation: async () => {
const response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error('API request failed');
return response.json();
},
errorResolvers: [
customErrorResolver({
canHandleError: keywordErrorFilterAny(['no credits']),
callback: async (error, attempt, config) => {
//TODO: Add credits to api.example.com
if (creditTopupSuccessful) {
return {
remainingAttempts: 0,
unrecoverable: false,
context: { lastError: error.message },
};
}
// If topup fails, we can't recover, so we return unrecoverable
return {
remainingAttempts: 0,
unrecoverable: true,
context: { lastError: error.message },
};
},
}),
// Fallback to linear backoff for any other error
delayErrorResolver({
configuration: { maxRetries: 3 },
}),
],
});
Multiple Parallel Operations
const results = await advancedRetryAll({
operations: [
() => fetch('https://api1.example.com').then(r => r.json()),
() => fetch('https://api2.example.com').then(r => r.json()),
],
errorResolvers: [
delayErrorResolver({
configuration: {
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 5000,
backoffMultiplier: 2,
},
}),
],
overallTimeout: 15000,
});
results.forEach((result, index) => {
if (result.success) {
console.log(`API ${index + 1} succeeded:`, result.result);
} else {
console.error(`API ${index + 1} failed:`, result.error);
}
});
Error Filtering
import {
advancedRetry,
customErrorResolver,
keywordErrorFilterAny,
keywordErrorFilterAll,
allErrorFilter,
anyErrorFilter,
} from 'advanced-retry';
// Retry only specific network errors
const result = await advancedRetry({
operation: async () => {
// Your operation
},
errorResolvers: [
customErrorResolver({
configuration: { maxRetries: 3 },
// Combine multiple filters
canHandleError: allErrorFilter([
keywordErrorFilterAny(['network', 'timeout']),
error => error instanceof NetworkError,
]),
callback: async (error, attempt, config) => ({
remainingAttempts: config.maxRetries - attempt,
unrecoverable: false,
context: { lastError: error.message },
}),
}),
],
});
Context Passing Between Operations and Retries
interface RetryContext {
lastAttemptTime: number;
serverUrl: string;
}
const result = await advancedRetry<string, RetryContext>({
operation: async context => {
const url = context?.data?.serverUrl ?? 'primary-server.com';
const response = await fetch(`https://${url}/api`);
return response.text();
},
errorResolvers: [
customErrorResolver<{ maxRetries: number }, string>({
configuration: { maxRetries: 3 },
callback: (error, attempt, config, context) => ({
remainingAttempts: config.maxRetries - attempt,
unrecoverable: false,
context: {
lastAttemptTime: Date.now(),
serverUrl: attempt > 0 ? 'backup-server.com' : 'primary-server.com',
},
}),
}),
],
});
Timeout and Abort Signal
const controller = new AbortController();
const result = await advancedRetry({
operation: async (context, signal) => {
const response = await fetch('https://api.example.com/data', {
signal, // Pass the abort signal to fetch
});
return response.json();
},
errorResolvers: [
delayErrorResolver({
configuration: {
maxRetries: 3,
initialDelayMs: 1000,
customDelay: ({ attempt }) =>
Math.min(1000 * Math.pow(2, attempt), 5000),
},
}),
],
overallTimeout: 10000, // 10 second total timeout
abortSignal: controller.signal,
});
// Abort operation if needed
setTimeout(() => controller.abort(), 5000);
API Reference
RetryOptions
interface RetryOptions<T, X> {
operation: (
retryContext?: RetryContext<X>,
abortSignal?: AbortSignal
) => Promise<T> | T;
errorResolvers?: Array<ErrorResolverBase<RetryContext<X>, X>>;
throwOnUnrecoveredError?: boolean;
overallTimeout?: number;
abortSignal?: AbortSignal;
}
RetryResult
interface RetryResult<T> {
success: boolean;
result?: T;
error?: Error;
totalAttemptsToSucceed?: number;
totalAttempts?: number;
totalDurationMs: number;
}
DelayedRetryPolicy
interface DelayedRetryPolicy<X = any> {
maxRetries: number;
initialDelayMs?: number;
maxDelayMs?: number;
backoffMultiplier?: number;
customDelay?: ({
attempt,
error,
context,
configuration,
}: {
attempt: number;
error: unknown;
context: RetryContext<X>;
configuration: DelayedRetryPolicy;
}) => number;
}
Requirements
- Tested on Node.js 16.0 and higher
- TypeScript 4.5+ (for TypeScript users)
Contributing
Contributions are always welcome! Please read our contributing guidelines first.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
Running Tests
npm install
npm test
Support
- 📫 For bugs and feature requests, please open an issue
- 💬 For questions and discussions, please use GitHub Discussions
- 📝 Read our documentation for more detailed information
FAQ
Why should I use Advanced-Retry instead of other retry libraries?
Advanced-Retry provides full type safety, flexible and customizable retry strategies where other packages are lacking.
If you need full customizability, with useful defaults, helper functions and a focus on good developer experience, this package is for you.
Small bonus: super light-weight dev framework, no dependencies and a focus on 100% test coverage (including branches).
Can I use Advanced-Retry in a browser environment?
Yes! Advanced-Retry is fully compatible with both Node.js and browser environments.
Are there any plans to add more features?
Yes, if you are missing any features, please open an issue to let me know.
Who uses this package?
Currently it is a personal project, as I found a lack of fitting retry library for my use cases. I use it in various projects, but be aware it is currently very early stage.