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

React+ASP.NET Core Axios接口调用偶发加载失败问题排查

问题根因排查

所有偶发异常均来自4个可稳定复现的逻辑缺陷,覆盖后端返回格式、前端异步处理、React执行机制三个层面,完全匹配你描述的所有异常表现:

  • 后端接口双重序列化缺陷(核心诱因)
    你在控制器中先手动用JsonConvert.SerializeObject将数组转为JSON字符串,再传入Ok()方法返回。ASP.NET Core默认会对所有返回值自动做JSON序列化,这就导致最终响应给前端的内容是被两层转义的字符串,而非预期的数组。
    你在Postman测试时肉眼看到返回内容是数组格式,实际是Postman自动格式化了转义字符,看起来正常而已。前端拿到200响应时,response.data是字符串类型而非数组,直接通过下标data[0]取值拿到的是字符串的单个字符(比如[、双引号),组装的业务对象全是无效值;如果字符串含特殊字符,会直接触发JS解析报错,中断后续写入缓存、更新状态的逻辑,对应你看到的「状态码200但缓存项创建成功、值为空」的现象。
    该问题偶发的原因是:当响应状态码为304走浏览器缓存时,部分浏览器会直接存储之前解析过的数组格式数据,不会触发解析错误;返回200新鲜响应时就会触发异常。
  • 前端异步请求无错误捕获,判断逻辑完全失效
    你写的if(promise)判断没有任何实际作用:await axios.get()只有在请求成功时才会返回响应对象,此时对象永远为真值;如果请求出现网络波动、跨域预检失败、被页面卸载中断,会直接抛出异步错误,因为没有try/catch包裹,整个请求函数会直接中断,既不会写缓存也不会更新状态。两个接口拆分在两个独立的useEffect中发起,任意一个请求抛错就会出现单接口加载失败的情况。
  • React严格模式触发竞态条件
    开发环境下React严格模式会让无依赖数组的useEffect在挂载时执行两次:第一次执行判断缓存不存在发起请求A,请求A还未返回时第二次effect触发,此时缓存仍为空又发起请求B。两个请求并发执行,返回顺序完全不可控,还可能被浏览器同域名并发连接限制排队、取消;如果请求B先返回写入缓存,请求A后返回用异常数据覆盖缓存,就会出现数据错乱。
    退出登录清空缓存后马上跳回页面时,两次effect执行叠加上页面卸载逻辑,很容易出现两个请求都被中断,对应你看到的「两个资源都无法加载」的现象。
  • 渲染阶段执行重定向违反React执行规则
    你在组件函数顶层(渲染阶段)直接写了sessionStorage判断+window.location跳转逻辑,该逻辑会在每次组件渲染时触发,包括严格模式的重复渲染、state更新触发的重渲染,跳转导致的页面卸载会直接中断正在进行中的请求,进一步提升失败概率。
修复方案

按以下顺序修改,所有偶发异常可全部解决:

1. 修复后端接口,移除多余的手动序列化

ASP.NET Core会自动处理对象JSON序列化,不需要手动转换,修改后代码如下:

[HttpGet("almacen/{dbhandle:int}/{idcaja:int}")]
public ActionResult<string[]> getAlmacen(int dbhandle, int idcaja) {
    string [] data = ConexionDB.Instance.Obtener_almacen_caja(dbhandle, idcaja);
    return Ok(data);
}

[HttpGet("cliente/{dbhandle:int}")]
public ActionResult<string[]> getcliente(int dbhandle) {
    string [] data = ConexionDB.Instance.Obtener_cliente(dbhandle);
    return Ok(data);
}

修改后前端拿到的response.data直接是标准数组,不存在转义问题。

2. 重构前端逻辑,解决竞态、错误捕获问题

把重定向逻辑挪到useEffect中,增加请求锁避免重复请求,统一做错误捕获,不要反复读写sessionStorage增加出错概率,修改后核心代码如下:

// 记得先导入useRef
import { useState, useEffect, useRef } from 'react';

const VentasMostrador = () => {
    // 原有状态定义保持不变
    const [cliente, setcliente] = useState(null);
    const [almacen, setalmacen] = useState(null);
    const [cajero, setcajero] = useState(null);
    const [productos, setproductos] = useState([]);
    const [cambio, setcambio] = useState(false);
    const [total, setTotal] = useState(0);
    const [show, setShow] = useState(false);
    const [productoSeleccionado, setProductoSeleccionado] = useState(null);
    const [seriesylotes, setseriesylotes] = useState(null);
    // 新增请求锁,避免严格模式重复发起请求
    const isFetchingRef = useRef(false);

    const getCliente = async () => {
        // 优先读缓存
        const cache = sessionStorage.getItem("GET_CLIENTE");
        if (cache) return JSON.parse(cache);
        const user = JSON.parse(localStorage.getItem("datauser"));
        const res = await axios.get(`https://localhost:5001/api/ventasmostrador/cliente/${user.DbHandle}`);
        const data = res.data;
        const infodata = { id: data[0], nombre: data[1], codigo: data[2] };
        sessionStorage.setItem("GET_CLIENTE", JSON.stringify(infodata));
        return infodata;
    };

    const getAlmacen = async () => {
        // 优先读缓存
        const cache = sessionStorage.getItem("GET_ALMACEN");
        if (cache) return JSON.parse(cache);
        const user = JSON.parse(localStorage.getItem("datauser"));
        const caja = JSON.parse(sessionStorage.getItem("ID_CAJA"));
        const res = await axios.get(`https://localhost:5001/api/ventasmostrador/almacen/${user.DbHandle}/${caja}`);
        const data = res.data;
        const infodata = {
            id: data[0],
            caja: data[1],
            idalmacen: data[2],
            nombrealmacen: data[3],
        };
        sessionStorage.setItem("GET_ALMACEN", JSON.stringify(infodata));
        return infodata;
    };

    // 统一把初始化、重定向逻辑放在同一个useEffect中
    useEffect(() => {
        // 重定向逻辑挪到effect内,避免渲染阶段触发
        if (!sessionStorage.getItem("ID_CAJA")) {
            window.location = "/PaginaPrincipal";
            return;
        }
        // 严格模式重复执行时直接跳过
        if (isFetchingRef.current) return;
        isFetchingRef.current = true;

        const initPageData = async () => {
            try {
                // 并行拉取所有初始化数据,统一捕获错误
                const [clienteRes, almacenRes, userRes] = await Promise.all([
                    getCliente(),
                    getAlmacen(),
                    Promise.resolve(JSON.parse(localStorage.getItem("datauser")))
                ]);
                setcliente(clienteRes);
                setalmacen(almacenRes);
                setcajero(userRes.Nombrecajero);
            } catch (err) {
                console.error("初始化数据加载失败:", err);
                // 失败时重置锁,支持手动重试
                isFetchingRef.current = false;
                // 可按需增加用户提示、重试逻辑
            }
        };
        initPageData();
    }, []);

    // 其余组件业务逻辑保持不变

3. 额外优化点

  • 退出登录清空缓存后,增加100ms左右的延时再做页面跳转,等当前页面逻辑执行完成再卸载,避免请求被中途中断。
  • 封装统一的storage读写工具函数,统一捕获JSON.parse/stringify的异常,避免存储内容损坏导致整个页面白屏。
  • 给axios配置统一baseURL,不要每次写全量请求路径,减少环境切换时的出错概率。

内容的提问来源于stack exchange,提问作者IamNotDeveloper

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 18:39:34