You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Axios响应拦截器更新请求头失败,触发DOMException错误

问题分析与解决

核心错误原因

报错本质是Authorization请求头被设置为函数而非合法字符串值,由两个关键问题导致:

  • 拦截器重复注册:useAPIClient Hook每次组件渲染都会新增请求/响应拦截器,拦截器堆积后同一请求被多次处理,最终导致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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 16:15:34