最近接手了一个项目,在本地调试时发现一个奇怪的现象:页面上“设备状态分布”和“维修状态统计”这两块区域,无论怎么刷新,始终显示着一组固定不变的数字:
// 设备状态分布数据constdeviceStatusData=ref([{value:18002,name:"正常"},{value:15,name:"维修中"},{value:0,name:"已报废"},]);// 维修状态数据constmaintenanceStatusData=ref([{value:13,name:"已完成"},{value:1,name:"处理中"},{value:1,name:"待受理"},]);这些数字看起来太“整齐”了——18002台正常、15台维修中……明显是开发初期为了占位写死的模拟数据。但问题是,代码里明明调用了后端接口去获取实时数据,为什么页面就是不更新?
从怀疑自己,到怀疑AI,再到怀疑人生
一开始,我本能地以为是自己写的逻辑有问题。毕竟这部分是我用 AI 辅助生成后又手动调整的。于是反复让 AI 检查字段映射、响应结构、赋值逻辑……甚至重写了三遍,结果毫无变化。
重启服务、清除缓存、换浏览器、开无痕模式……能想到的排查手段都试了,数据还是纹丝不动。那一刻,真的有点怀疑是不是自己漏看了什么低级错误。
直到我做了一件最简单也最有效的事:加一行console.log。
console.log('开始调用 getLabDeviceStatus 接口');constdeviceStatus=awaitgetLabDeviceStatus();console.log('接口返回:',deviceStatus);刷新页面,控制台一片寂静——连第一行日志都没出现。
这说明:那段看似会被执行的代码,压根就没跑进去。
问题藏在“统一异常处理”的温柔陷阱里
顺着调用链往上找,我定位到了一个叫fetchData的函数。打开一看,真相浮出水面:
constfetchData=async()=>{try{updateTime();// 先请求实验室使用状态labStatusData.value[0].value=awaitfetchTotal(listLaboratory,{status:'1'});labStatusData.value[1].value=awaitfetchTotal(listLaboratory,{status:'2'});labStatusData.value[2].value=awaitfetchTotal(listLaboratory,{status:'0'});// 再请求资产状态constassetStatus=awaitgetLabAssetStatus();if(assetStatus&&assetStatus.code===200){benchData.value=assetStatus.data;}// 最后请求设备状态(关键!)constdeviceStatus=awaitgetLabDeviceStatus();// ...更新 deviceStatusData 和 maintenanceStatusData}catch(error){console.error('获取数据失败:',error);}}问题就出在这个大的try...catch+ 串行await上。
整个函数被一个try包裹,所有接口按顺序await执行。这意味着:只要前面任何一个接口抛出异常(比如第一个fetchTotal因权限或参数问题失败),程序会立刻跳进catch块,后面的接口根本不会被发起。
而恰好,那个fetchTotal接口在当前测试环境下确实会失败(返回 403 或空数据),但它失败得悄无声息——只在控制台留下一句模糊的“获取数据失败”,没有指明具体位置。于是,后面所有依赖真实接口的数据,全都“继承”了初始化时的静态值,看起来就像功能没接通一样。
解法:让各个接口独自处理其响应状态
要解决这个问题,核心原则就一条:不同数据模块之间应相互独立,一个失败不应影响其他。
我提供了两种改造方案:
方案一:细粒度try...catch
给每组逻辑套上自己的异常处理边界:
constfetchData=async()=>{updateTime();// 实验室使用状态(独立处理)try{labStatusData.value[0].value=awaitfetchTotal(listLaboratory,{status:'1'});// ...}catch(e){console.error('实验室状态加载失败:',e);}// 资产状态(独立处理)try{constres=awaitgetLabAssetStatus();if(res?.code===200)benchData.value=res.data;}catch(e){console.error('资产状态加载失败:',e);}// 设备状态(独立处理)try{constres=awaitgetLabDeviceStatus();if(res?.code===200){// 更新 deviceStatusData 和 maintenanceStatusData}}catch(e){console.error('设备状态加载失败:',e);}}这种方法是AI写的,我抨击他写的又臭又长,于是我让他使用链式调用,也就是方案二
方案二:链式调用 + 并发(推荐)
直接用.then().catch(),不仅隔离错误,还实现并发:
constfetchData=async()=>{updateTime();// 这三个请求现在是并发的!fetchTotal(listLaboratory,{status:'1'}).then(v=>labStatusData.value[0].value=v).catch(console.error);fetchTotal(listLaboratory,{status:'2'}).then(v=>labStatusData.value[1].value=v).catch(console.error);fetchTotal(listLaboratory,{status:'0'}).then(v=>labStatusData.value[2].value=v).catch(console.error);getLabAssetStatus().then(res=>res?.code===200&&(benchData.value=res.data)).catch(console.error);getLabDeviceStatus().then(res=>{if(res?.code===200){// 更新设备和维修状态}}).catch(console.error);}改完之后,再刷新页面——设备状态和维修状态的数据立刻变成了接口返回的真实数字,控制台也清晰地打印出了各接口的日志。那种“终于活了”的感觉,真的很爽。
反思:几个值得警惕的开发习惯
这次排查让我意识到几个容易被忽视的问题:
不要为了“简洁”牺牲鲁棒性
把多个独立操作塞进同一个try...catch看似代码干净,实则埋下了连锁失败的隐患。错误边界必须清晰。静态占位数据要有明确标识
建议在开发阶段给模拟数据加上[DEV]前缀,或仅在process.env.NODE_ENV === 'development'时启用。否则上线后极易掩盖接口未生效的问题。日志要具体到可定位
“获取数据失败”这种日志几乎没用。应该记录接口名称、参数、错误码,比如:fetchTotal(status=1) failed: 403 Forbidden。大屏项目尤其需要容错设计
数据大屏往往需要长时间运行、对接多个数据源。某个接口临时不可用是常态,系统必须能“局部失效,整体可用”。
结语
这不是一个高深的技术难题,而是一个关于模块独立性和错误隔离意识的经典案例。在多人协作或接手遗留项目时,尤其要警惕那些“看起来很整洁”但隐藏耦合风险的写法。
下次当你看到页面上某些数据“顽固地”保持不变时,别急着重写逻辑——先问问自己:是不是有人把所有鸡蛋,放在了一个会碎的篮子里?