Events
The event system lets you react to every stage of the retry lifecycle. Use it for logging, dashboards, analytics, and custom side effects — without touching request code.
Subscribing to events
import { createRetryer } from 'axios-retryer';
import { createMetricsPlugin } from 'axios-retryer/plugins/MetricsPlugin';
// Chain .use() on the initializer so TypeScript knows about plugin events
const retryer = createRetryer().use(createMetricsPlugin());
// Chain multiple subscriptions (core + plugin events on the same manager)
retryer
.on('onRetryProcessStarted', () => {
console.log('Retry process started');
})
.on('beforeRetry', (config) => {
console.log(`About to retry: ${'${config.method?.toUpperCase()} ${config.url}'}`);
})
.on('afterRetry', (config, success, error) => {
console.log(`Retry attempt ${success ? 'succeeded' : 'failed'}`, error?.message);
})
.on('onRetryProcessFinished', () => {
console.log('All retry attempts complete');
})
.on('onFailure', (config) => {
analytics.track('api_failure', { url: config.url });
})
.on('onMetricsUpdated', (metrics) => {
updateDashboard(metrics);
}); Unsubscribing
const handler = (config: import('axios').AxiosRequestConfig) => console.log(config.url);
retryer.on('beforeRetry', handler);
// Later, when cleanup is needed:
retryer.off('beforeRetry', handler); Plugin-specific events
TypeScript only widens on() / off() after use() when you keep the result in the type of your variable.
Prefer chaining every plugin on the initializer (createRetryer().use(a).use(b)), or assign each step (let m = createRetryer(); m = m.use(a);).
Calling retryer.use(plugin) and discarding the return value leaves retryer typed without that plugin’s events.
import { createRetryer } from 'axios-retryer';
import { createTokenRefreshPlugin } from 'axios-retryer/plugins/TokenRefreshPlugin';
import { createMetricsPlugin } from 'axios-retryer/plugins/MetricsPlugin';
// Chained .use(): event map is the intersection of every plugin you attach
export const retryer = createRetryer({ retries: 3 })
.use(createMetricsPlugin())
.use(
createTokenRefreshPlugin(async (axios) => {
const { data } = await axios.post('/auth/refresh');
return { token: data.accessToken };
}),
);
retryer.on('onMetricsUpdated', (metrics) => {
console.log('Retries:', metrics.successfulRetries);
});
retryer.on('onTokenRefreshed', (token) => {
console.log('Token refreshed:', token);
}); Explicit event map (optional)
If you split use() across statements but still want full typing, pass a composed generic to new RetryManager<…>() (see each plugin’s exported *PluginEvents type):
import { RetryManager } from 'axios-retryer';
import type { MetricsPluginEvents } from 'axios-retryer/plugins/MetricsPlugin';
import type { TokenRefreshPluginEvents } from 'axios-retryer/plugins/TokenRefreshPlugin';
import { createMetricsPlugin } from 'axios-retryer/plugins/MetricsPlugin';
import { createTokenRefreshPlugin } from 'axios-retryer/plugins/TokenRefreshPlugin';
// Optional: name the composed event map (e.g. for shared module typings)
type AppRetryerEvents = MetricsPluginEvents & TokenRefreshPluginEvents;
const retryer = new RetryManager<AppRetryerEvents>({ retries: 3 });
retryer.use(createMetricsPlugin());
retryer.use(
createTokenRefreshPlugin(async (axios) => {
const { data } = await axios.post('/auth/refresh');
return { token: data.accessToken };
}),
);
retryer.on('onMetricsUpdated', (snapshot) => {
console.log(snapshot.successfulRetries);
});
retryer.on('onTokenRefreshed', (token) => {
console.log('Token length', token.length);
}); Event reference
| Event | Payload | Plugin required | Description |
|---|---|---|---|
onRetryProcessStarted | — | — | Retry cycle begins for a request |
beforeRetry | AxiosRequestConfig | — | Before each individual retry attempt |
afterRetry | config, success, error? | — | After each individual retry attempt; error present when success is false |
onRetryScheduled | delayMs, config | — | A retry was scheduled after the given delay |
onFailure | AxiosRequestConfig | — | A single attempt failed (per-attempt hook) |
onRetryProcessFinished | — | — | All retry attempts complete (success or terminal failure) |
onRequestQueued | AxiosRetryerRequestQueuedEvent | — | Request entered the concurrency queue |
onRequestDispatched | AxiosRetryerRequestDispatchedEvent | — | Request left the queue and was dispatched |
onRequestSucceeded | AxiosRetryerRequestSucceededEvent | — | Request completed successfully (initial or after retries) |
onRequestError | AxiosRetryerRequestErrorEvent | — | Terminal failure (all retries exhausted or no-retry path) |
onRequestCancelled | requestId: string | — | A request was cancelled |
onInternetConnectionError | AxiosRequestConfig | — | Network-level error (no response) |
onMetricsUpdated | RetryMetrics | MetricsPlugin | Metrics snapshot after each request cycle |
onBeforeTokenRefresh | — | TokenRefreshPlugin | Refresh cycle started (before the callback runs) |
onTokenRefreshed | token: string | TokenRefreshPlugin | New token applied; not emitted if the callback skips by returning no token (opt-out) |
onTokenRefreshFailed | — | TokenRefreshPlugin | All refresh attempts failed; not emitted on opt-out skip |
onBlockingRequestFailed | config | — | A blocking request (≥ blockingPriorityThreshold) failed terminally |
onAllBlockingRequestsResolved | — | — | All in-flight blocking requests (≥ threshold) succeeded; gate lifted. Not emitted on blocker failure or cancellation (see concurrency guide) |
onManualRetryProcessStarted | — | ManualRetryPlugin | Manual replay begins |
Real-time metrics dashboard pattern
import { createRetryer } from 'axios-retryer';
import { createMetricsPlugin } from 'axios-retryer/plugins/MetricsPlugin';
const retryer = createRetryer().use(createMetricsPlugin());
retryer.on('onMetricsUpdated', (metrics) => {
myDashboard.update({
successRate: metrics.successfulRetries / (metrics.totalRequests || 1),
failureRate: metrics.completelyFailedRequests,
avgDelay: metrics.averageRetryDelay,
});
});