Axios响应拦截器更新请求头失败,触发DOMException错误
问题分析与解决
核心错误原因
报错本质是Authorization请求头被设置为函数而非合法字符串值,由两个关键问题导致:
- 拦截器重复注册:
useAPIClientHook每次组件渲染都会新增请求/响应拦截器,拦截器堆积后同一请求被多次处理,最终导致headers被错误覆盖为Axios内部方法函数。 - 条件判断逻辑错误:原代码中
(token !== "" || token !== null)永远为true(token不可能同时是空字符串和null),当token为空时仍会尝试设置Authorization头,触发异常。
修复步骤
1. 拦截器注册与清理
用useEffect包裹拦截器逻辑,组件卸载时移除拦截器避免堆积:
import { useEffect } from 'react'; import { AxiosRequestConfig } from "axios"; import { useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { APIClient } from "../api"; import { requestRefresh, selectToken, useAppDispatch } from "../store"; export const useAPIClient = () => { const navigate = useNavigate(); const dispatch = useAppDispatch(); const token = useSelector(selectToken); useEffect(() => { // 请求拦截器 const requestInterceptor = APIClient.interceptors.request.use( (config: AxiosRequestConfig) => { // 仅当token存在时设置请求头 if (config?.headers && token) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error) ); // 响应拦截器 const responseInterceptor = APIClient.interceptors.response.use( (response) => response, async (error) => { if (error?.response && token) { const prevRequest = error.config; if (error.response.status === 401 && !prevRequest._retry) { prevRequest._retry = true; try { const refreshRes = await dispatch(requestRefresh()).unwrap(); const newToken = refreshRes.data.accessToken; // 确保赋值合法字符串头 prevRequest.headers['Authorization'] = `Bearer ${newToken}`; return APIClient(prevRequest); } catch (refreshErr) { navigate("/sign_in", { replace: true }); return Promise.reject(refreshErr); } } else if (error.response.status === 403) { navigate("/sign_in", { replace: true }); return Promise.reject(error); } } return Promise.reject(error); } ); // 组件卸载时移除拦截器 return () => { APIClient.interceptors.request.eject(requestInterceptor); APIClient.interceptors.response.eject(responseInterceptor); }; }, [token, dispatch, navigate]); return APIClient; };
2. 修正Headers赋值兼容性
如果prevRequest.headers是Axios封装的AxiosHeaders对象,改用set方法确保赋值正确:
import { AxiosHeaders } from 'axios'; // 在重试逻辑中替换原赋值代码 if (prevRequest.headers instanceof AxiosHeaders) { prevRequest.headers.set('Authorization', `Bearer ${newToken}`); } else { prevRequest.headers['Authorization'] = `Bearer ${newToken}`; }
额外优化:防止重复刷新
当多个请求同时触发401时,添加锁机制避免重复发起刷新请求:
// 全局变量标记刷新状态 let isRefreshing = false; let failedRequestQueue: Array<{ resolve: (token: string) => void; reject: (error: any) => void }> = []; // 在响应拦截器的401处理逻辑中修改: if (error.response.status === 401 && !prevRequest._retry) { if (isRefreshing) { // 等待刷新完成后重试 return new Promise((resolve, reject) => { failedRequestQueue.push({ resolve, reject }); }).then(newToken => { prevRequest.headers['Authorization'] = `Bearer ${newToken}`; return APIClient(prevRequest); }).catch(err => Promise.reject(err)); } isRefreshing = true; prevRequest._retry = true; try { const refreshRes = await dispatch(requestRefresh()).unwrap(); const newToken = refreshRes.data.accessToken; // 重试队列中所有请求 failedRequestQueue.forEach(item => item.resolve(newToken)); failedRequestQueue = []; prevRequest.headers['Authorization'] = `Bearer ${newToken}`; return APIClient(prevRequest); } catch (refreshErr) { failedRequestQueue.forEach(item => item.reject(refreshErr)); failedRequestQueue = []; navigate("/sign_in", { replace: true }); return Promise.reject(refreshErr); } finally { isRefreshing = false; } }
内容的提问来源于stack exchange,提问作者BirukAbreham
相关产品推荐
相关产品推荐

