Docs

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

EventPayloadPlugin requiredDescription
onRetryProcessStartedRetry cycle begins for a request
beforeRetryAxiosRequestConfigBefore each individual retry attempt
afterRetryconfig, success, error?After each individual retry attempt; error present when success is false
onRetryScheduleddelayMs, configA retry was scheduled after the given delay
onFailureAxiosRequestConfigA single attempt failed (per-attempt hook)
onRetryProcessFinishedAll retry attempts complete (success or terminal failure)
onRequestQueuedAxiosRetryerRequestQueuedEventRequest entered the concurrency queue
onRequestDispatchedAxiosRetryerRequestDispatchedEventRequest left the queue and was dispatched
onRequestSucceededAxiosRetryerRequestSucceededEventRequest completed successfully (initial or after retries)
onRequestErrorAxiosRetryerRequestErrorEventTerminal failure (all retries exhausted or no-retry path)
onRequestCancelledrequestId: stringA request was cancelled
onInternetConnectionErrorAxiosRequestConfigNetwork-level error (no response)
onMetricsUpdatedRetryMetricsMetricsPluginMetrics snapshot after each request cycle
onBeforeTokenRefreshTokenRefreshPluginRefresh cycle started (before the callback runs)
onTokenRefreshedtoken: stringTokenRefreshPluginNew token applied; not emitted if the callback skips by returning no token (opt-out)
onTokenRefreshFailedTokenRefreshPluginAll refresh attempts failed; not emitted on opt-out skip
onBlockingRequestFailedconfigA blocking request (≥ blockingPriorityThreshold) failed terminally
onAllBlockingRequestsResolvedAll in-flight blocking requests (≥ threshold) succeeded; gate lifted. Not emitted on blocker failure or cancellation (see concurrency guide)
onManualRetryProcessStartedManualRetryPluginManual 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,
  });
});
Error Classes → Plugins