React应用中Axios拦截器刷新Token引发无限循环问题排查与修复
Hey, I've run into this exact issue before—concurrent requests all trying to refresh the token at once creates a total mess. Let's break down why this happens and how to fix it:
The Root Problem
When multiple requests fire at the same time and all detect the access token is expired, every single one triggers a refresh token request. This leads to duplicate refresh calls, race conditions when updating localStorage, and eventually that infinite loop you're seeing.
The Solution: Add a Refresh Lock & Request Queue
We need to ensure only one refresh request runs at a time, and queue up other requests until the new token is available. Here's how to adjust your code:
import axios from 'axios' import jwt_decode from "jwt-decode"; import dayjs from 'dayjs' const baseURL = 'http://127.0.0.1:8000' let authTokens = localStorage.getItem('authTokens') ? JSON.parse(localStorage.getItem('authTokens')) : null; // Add these two variables to track refresh state and queue pending requests let isRefreshing = false; let requestsQueue = []; const axiosInstance = axios.create({ baseURL, headers: { Authorization: `Bearer ${authTokens?.access}` } }); axiosInstance.interceptors.request.use(async req => { if (!authTokens) { authTokens = localStorage.getItem('authTokens') ? JSON.parse(localStorage.getItem('authTokens')) : null; req.headers.Authorization = `Bearer ${authTokens?.access}`; } const user = jwt_decode(authTokens.access); const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1; // If token isn't expired, just proceed with the request if (!isExpired) return req; // Case 1: We're already refreshing the token—add this request to the queue if (isRefreshing) { return new Promise((resolve) => { requestsQueue.push(() => { req.headers.Authorization = `Bearer ${authTokens.access}`; resolve(axiosInstance(req)); }); }); } // Case 2: Start the refresh process isRefreshing = true; try { const response = await axios.post(`${baseURL}/api/token/refresh/`, { refresh: authTokens.refresh }); // Update tokens in localStorage and our in-memory variable localStorage.setItem('authTokens', JSON.stringify(response.data)); authTokens = response.data; // Update the current request's header with the new token req.headers.Authorization = `Bearer ${response.data.access}`; // Process all queued requests with the fresh token requestsQueue.forEach(callback => callback()); requestsQueue = []; return req; } catch (error) { // Handle refresh failure (e.g., refresh token expired) localStorage.removeItem('authTokens'); authTokens = null; // Redirect to login or handle error based on your app's flow window.location.href = '/login'; return Promise.reject(error); } finally { // Reset the refresh lock no matter if refresh succeeded or failed isRefreshing = false; } }); export default axiosInstance;
Key Changes Explained
isRefreshingflag: Acts as a lock to prevent multiple concurrent refresh requests. Once a refresh starts, other requests wait instead of firing their own refresh calls.requestsQueue: Stores requests that came in while a refresh was in progress. After getting the new token, we replay all these requests with the updated authorization header.- Promise wrapping for queued requests: Ensures pending requests don't fail immediately—they wait until the new token is ready, then retry automatically.
- Error handling: If the refresh token is expired (or any other error occurs), we clean up localStorage and redirect to login, which stops the loop in its tracks.
This should eliminate the infinite loop and make your concurrent requests behave correctly!
内容的提问来源于stack exchange,提问作者Laurel

