Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
Ajax

AJAX: Use Axios Interceptors to Automatically Retry Failed Requests

- 03.02.26 - ErcanOPAK

Network hiccups causing failed API requests? Axios interceptors can automatically retry failed requests before showing errors to users.

Install Axios:

npm install axios

The Basic Retry Interceptor:

import axios from 'axios';

// Configure retry
axios.interceptors.response.use(
    response => response, // Success - return as is
    
    async error => {
        const config = error.config;
        
        // If no config or already retried max times, reject
        if (!config || !config.retry) {
            return Promise.reject(error);
        }
        
        // Set retry count
        config.__retryCount = config.__retryCount || 0;
        
        // Check if we've maxed out retries
        if (config.__retryCount >= config.retry) {
            return Promise.reject(error);
        }
        
        // Increase retry count
        config.__retryCount += 1;
        
        // Delay before retry
        const delay = config.retryDelay || 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
        
        // Retry request
        return axios(config);
    }
);

// Usage: Add retry config to requests
axios.get('/api/data', {
    retry: 3,           // Retry 3 times
    retryDelay: 1000    // Wait 1 second between retries
})
.then(response => console.log(response.data))
.catch(error => console.error('Failed after 3 retries'));

Exponential Backoff:

axios.interceptors.response.use(
    response => response,
    
    async error => {
        const config = error.config;
        
        if (!config || !config.retry) {
            return Promise.reject(error);
        }
        
        config.__retryCount = config.__retryCount || 0;
        
        if (config.__retryCount >= config.retry) {
            return Promise.reject(error);
        }
        
        config.__retryCount += 1;
        
        // Exponential backoff: 1s, 2s, 4s, 8s...
        const delay = Math.pow(2, config.__retryCount) * 1000;
        
        console.log(`Retry ${config.__retryCount}/${config.retry} after ${delay}ms`);
        
        await new Promise(resolve => setTimeout(resolve, delay));
        
        return axios(config);
    }
);

// First retry: 2 seconds
// Second retry: 4 seconds
// Third retry: 8 seconds

Retry Only on Specific Status Codes:

axios.interceptors.response.use(
    response => response,
    
    async error => {
        const config = error.config;
        const status = error.response?.status;
        
        // Only retry on network errors or 5xx server errors
        const shouldRetry = !status || status >= 500;
        
        if (!shouldRetry || !config?.retry) {
            return Promise.reject(error);
        }
        
        // Don't retry client errors (4xx)
        if (status >= 400 && status < 500) {
            console.log('Client error, not retrying:', status);
            return Promise.reject(error);
        }
        
        config.__retryCount = config.__retryCount || 0;
        
        if (config.__retryCount >= config.retry) {
            return Promise.reject(error);
        }
        
        config.__retryCount += 1;
        
        const delay = config.retryDelay || 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
        
        return axios(config);
    }
);

// Retries on:
// - Network failures (no status)
// - 500, 502, 503, 504 (server errors)

// Does NOT retry on:
// - 400 Bad Request
// - 401 Unauthorized  
// - 404 Not Found
// (These won't fix themselves by retrying)

Global Retry Configuration:

// Set defaults for all requests
axios.defaults.retry = 3;
axios.defaults.retryDelay = 1000;

// Now all requests auto-retry
axios.get('/api/users');     // Auto-retries 3 times
axios.post('/api/orders');   // Auto-retries 3 times

// Override per request
axios.get('/api/critical', {
    retry: 5,          // Retry more for critical endpoints
    retryDelay: 2000
});

Show Retry Progress to User:

axios.interceptors.response.use(
    response => response,
    
    async error => {
        const config = error.config;
        
        if (!config?.retry) {
            return Promise.reject(error);
        }
        
        config.__retryCount = config.__retryCount || 0;
        
        if (config.__retryCount >= config.retry) {
            showToast('Request failed after multiple attempts', 'error');
            return Promise.reject(error);
        }
        
        config.__retryCount += 1;
        
        // Show retry notification
        showToast(
            `Connection issue. Retrying (${config.__retryCount}/${config.retry})...`,
            'warning'
        );
        
        const delay = Math.pow(2, config.__retryCount) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
        
        return axios(config);
    }
);

// User sees:
// "Connection issue. Retrying (1/3)..."
// "Connection issue. Retrying (2/3)..."
// Either: "Success!" or "Request failed after multiple attempts"

Retry with Authentication Refresh:

let isRefreshing = false;
let failedQueue = [];

axios.interceptors.response.use(
    response => response,
    
    async error => {
        const originalRequest = error.config;
        
        // If 401 and hasn't been retried yet
        if (error.response?.status === 401 && !originalRequest._retry) {
            if (isRefreshing) {
                // Queue this request while token refreshes
                return new Promise((resolve, reject) => {
                    failedQueue.push({ resolve, reject });
                })
                .then(token => {
                    originalRequest.headers.Authorization = `Bearer ${token}`;
                    return axios(originalRequest);
                });
            }
            
            originalRequest._retry = true;
            isRefreshing = true;
            
            try {
                // Refresh token
                const { data } = await axios.post('/api/refresh-token');
                const newToken = data.access_token;
                
                // Update token
                axios.defaults.headers.common.Authorization = `Bearer ${newToken}`;
                
                // Retry all queued requests
                failedQueue.forEach(promise => {
                    promise.resolve(newToken);
                });
                failedQueue = [];
                
                // Retry original request
                originalRequest.headers.Authorization = `Bearer ${newToken}`;
                return axios(originalRequest);
                
            } catch (refreshError) {
                // Refresh failed - logout user
                failedQueue.forEach(promise => {
                    promise.reject(refreshError);
                });
                failedQueue = [];
                
                // Redirect to login
                window.location.href = '/login';
                return Promise.reject(refreshError);
                
            } finally {
                isRefreshing = false;
            }
        }
        
        return Promise.reject(error);
    }
);

// User stays logged in even if token expires mid-session!

Cancel Retry on User Action:

const CancelToken = axios.CancelToken;
let cancel;

const request = axios.get('/api/slow-endpoint', {
    retry: 5,
    retryDelay: 2000,
    cancelToken: new CancelToken(c => cancel = c)
});

// User clicks "Cancel" button
document.getElementById('cancel-btn').addEventListener('click', () => {
    cancel('User cancelled request');
});

Related posts:

Ajax Revolution: How Fetch API and Async/Await Replace jQuery.ajax()

AJAX — Missing credentials Breaks Auth Cookies

Silent JSON Parsing Failures

Post Views: 2

Post navigation

JavaScript: Use Optional Chaining to Prevent ‘Cannot Read Property of Undefined’ Errors
Git: Undo Last Commit Without Losing Changes (3 Different Scenarios)

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

March 2026
M T W T F S S
 1
2345678
9101112131415
16171819202122
23242526272829
3031  
« Feb    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (938)
  • How to add default value for Entity Framework migrations for DateTime and Bool (836)
  • Get the First and Last Word from a String or Sentence in SQL (828)
  • How to select distinct rows in a datatable in C# (801)
  • How to make theater mode the default for Youtube (736)
  • Add Constraint to SQL Table to ensure email contains @ (575)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (554)
  • Average of all values in a column that are not zero in SQL (523)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (477)
  • Find numbers with more than two decimal places in SQL (441)

Recent Posts

  • C#: Saving Memory with yield return (Lazy Streams)
  • C#: Why Records are Better Than Classes for Data DTOs
  • C#: Creating Strings Without Memory Pressure with String.Create
  • SQL: Protecting Sensitive Data with Dynamic Data Masking
  • SQL: Writing Readable Queries with Common Table Expressions (CTE)
  • .NET Core: Handling Errors Gracefully with Middleware
  • .NET Core: Mastering Service Lifetimes (A Visual Guide)
  • Git: Surgical Stashing – Don’t Save Everything!
  • Git: Writing Commits That Your Future Self Won’t Hate
  • Ajax: Improving Perceived Speed with Skeleton Screens

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (938)
  • How to add default value for Entity Framework migrations for DateTime and Bool (836)
  • Get the First and Last Word from a String or Sentence in SQL (828)
  • How to select distinct rows in a datatable in C# (801)
  • How to make theater mode the default for Youtube (736)

Recent Posts

  • C#: Saving Memory with yield return (Lazy Streams)
  • C#: Why Records are Better Than Classes for Data DTOs
  • C#: Creating Strings Without Memory Pressure with String.Create
  • SQL: Protecting Sensitive Data with Dynamic Data Masking
  • SQL: Writing Readable Queries with Common Table Expressions (CTE)

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com