我修了一个注释代码,结果引出一连串线上 BUG…

概要

本文涉及:axios 拦截器、重复请求取消、请求 / 响应参数差异、axios 数据序列化、调用栈溢出排查

适合:遇到接口重复调用、拦截器异常、树形数据渲染崩溃的前端开发者

背景:附件上传异常,引出旧优化逻辑

某天,线上项目收到用户反馈:附件上传时,同时上传两个会出错。

排查后发现:旧系统框架使用 element-ui,新系统使用 ant-design-vue。两个框架的 upload 组件参数格式不一致,直接导致上传异常。

由于存量代码较多,完全重构上传组件成本较高,因此决定:利用项目中已有的 "重复请求优化" 能力,临时规避组件差异问题。

项目里原本就有一个优化逻辑:短时间内重复调用同一接口,只保留最后一次,前面的自动取消。

但同事反馈:这段代码看起来存在,但实际并未生效,重复接口并没有被合并。于是,这个问题转到了我这里。

理清来龙去脉,开始排查。

第一层问题:拦截器逻辑被注释失效

首先梳理重复请求优化的整体逻辑:通过 请求拦截器 + 响应拦截器 配合实现:

  • 把当前接口信息存入一个 pending 容器
  • 接口返回前,如果再次触发相同请求,取消前一次,只保留最后一次
  • 请求结束后,从容器中移除

在检查拦截器代码时发现:

请求拦截器中,removePending 一行被注释掉了,导致整个取消逻辑不完整。

(上图:请求拦截器代码,核心移除逻辑被注释)

查看历史记录,这是项目初期就被注释的代码,前序团队并未启用。为了解决附件上传问题,我先将这行代码恢复启用。

通知测试验证,附件上传功能恢复正常,问题暂时修复。

但真正的问题,才刚刚开始浮现。

第二层问题:树结构页面数据展示异常,调用栈溢出

复测通过后不久,测试反馈:某树表格页面数据无法展示。

控制台提示:调用栈溢出(Maximum call stack size exceeded)

(上图:树表格页面接口报错,调用栈溢出)

这个功能长期稳定运行,理论上不应该突然出问题。

同事提议:把刚才修改的拦截器代码恢复注释,试试看。

结果:注释掉 removePending 后,页面立刻恢复正常。

问题很明确:启用重复请求拦截逻辑 → 触发新 BUG:树数据展示失败、栈溢出。

先贴出核心工具函数 removePending:作用是遍历 pending 容器,匹配相同请求并执行取消。

(上图:removePending 核心逻辑)

根因一:请求 / 响应拦截器参数结构不一致

排查到这里,大部分同学应该已经能看出问题:

请求拦截器 与 响应拦截器 都将自身默认的参数传给了 removePending 函数,但,两个拦截器的默认参数的格式,是不同的!

问题,就出在这里

(上图:请求拦截器参数结构:config)

  1. 请求拦截器参数

    (上图:响应拦截器参数结构:response,需通过 .config 获取请求配置

  2. 响应拦截器参数 确认:响应拦截器里,应该传 response.config,而不是直接传 response。

直接传 response 会发生什么?response 中包含完整的接口返回 data,对某些大体积数据(比如深度树结构)执行 qs.stringify,会因为递归深度过高,直接爆调用栈

这就是树页面崩溃、栈溢出的直接原因。

修复方式:响应拦截器调用 removePending 时,统一使用 .config

修改后,栈溢出问题消失,页面恢复正常。

第三层问题:pending 容器只增不减,内存累积

功能虽然恢复,但我在复查时发现一个隐藏问题:

接口执行完毕后,pending 容器并没有完全清空,部分请求一直残留。

上图:pending 容器不断累积,未正常清空

逻辑上:

  • 请求拦截器:add → 加入容器
  • 响应拦截器:remove → 移出容器流程是闭环的,不应该残留。

继续排查,最终定位到:config.data

分别打印对比:

  • 请求拦截器中的 config.data

  • 响应拦截器中的 config.data

现象:

  • 请求拦截器中:config.data对象
  • 响应拦截器中:config.data 变成了 JSON 字符串

但控制台显示却是相同的,两者的 data 均为下图所示。

这里有一个前端非常经典的 "隐形坑":控制台打印对象是懒加载引用,看到的不一定是代码执行时的真实值。

根因二:axios 自动序列化 data

回顾一下 removePending 函数,问题就出在 config.data 上

真正原因来自 axios 底层行为:

axios 会在 请求拦截器执行完毕后、发送请求前 ,自动把 config.data 从 JS 对象序列化为 JSON 字符串。

这就导致:

  • 请求拦截器操作的是:原始对象
  • 响应拦截器拿到的是:序列化后的字符串

两者在 removePending 里做匹配、拼接、序列化时,自然无法正确匹配,最终表现为:部分请求能移除、部分请求移除失败,容器只增不减。

最终修复方案:在生成请求唯一标识时,统一处理 data 格式,对 JOSN 格式进行解析,保证请求 / 响应阶段类型一致。

修复后,pending 容器能够正常添加、正常移除,逻辑完全闭环。

修复后的代码:

最终,这个优化代码的逻辑与实现,完全正常了!

总结与复盘

这是一个典型的 架构阶段遗留、长期被掩盖、因业务 / 框架变更才暴露 的复合型 BUG。

整个问题链路可以梳理为:

  1. 历史代码:重复请求拦截逻辑被注释,处于半失效状态
  2. 业务场景:新旧框架 upload 组件参数不一致,需要依赖拦截器规避
  3. 第一层 BUG:拦截器参数结构不匹配,直接传 response 导致大数据序列化爆栈
  4. 第二层 BUG:axios 自动序列化 data,导致请求 / 响应阶段 config.data 类型不一致,匹配失败
  5. 最终表现:树结构页面崩溃、pending 容器累积、隐蔽且难定位

核心结论

  • 请求 / 响应拦截器参数结构不同,不能直接混用
  • 避免对大量级、深度嵌套数据无脑执行 qs.stringify
  • axios 会自动序列化 data,请求 / 响应阶段类型可能不一致
  • 控制台打印对象 ≠ 代码运行时真实值,务必以类型 / 快照为准

这类问题隐蔽性强、和底层框架强相关,也是前端工程化中非常典型的 "隐形坑",记录下来供大家参考避坑。

相关推荐
子兮曰1 天前
async/await高级模式:async迭代器、错误边界与并发控制
前端·javascript·github
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
GIS之路1 天前
ArcGIS Pro 中的 Notebooks 入门
前端
IT_陈寒1 天前
React状态管理终极对决:Redux vs Context API谁更胜一筹?
前端·人工智能·后端
Kagol1 天前
TinyVue 支持 Skills 啦!现在你可以让 AI 使用 TinyVue 组件搭建项目
前端·agent·ai编程
柳杉1 天前
从零打造 AI 全球趋势监测大屏
前端·javascript·aigc
simple_lau1 天前
Cursor配置MasterGo MCP:一键读取设计稿生成高还原度前端代码
前端·javascript·vue.js
睡不着先生1 天前
如何设计一个真正可扩展的表单生成器?
前端·javascript·vue.js
天蓝色的鱼鱼1 天前
模块化与组件化:90%的前端开发者都没搞懂的本质区别
前端·架构·代码规范
明君879971 天前
Flutter 如何给图片添加多行文字水印
前端·flutter