实际场景
前端,通过表单,选择两个文件并提交。
后端,返回两个文件的数据,每个文件约有4000条数据,总共有8000多条数据。
前端,在表格中将文件中的数据展示出来。
问题表现
提交之后,只有一个文件成功展示了出来,另一个文件没能展示,需要再次点击提交才会出现。
问题代码
可以简单地将setTimeout中的300改为1000解决问题。
当时的收到的意见是——
“B表出不来,是因为A表的数据量大,B表的数据量也大,在数据与空间对接时,B表查看控制的数据还没准备好。而修改之后的这种写法感觉也不太好,事实上应该是数据准备好了,自动加载”。
其实问题原因的定位没有问题,但第二天又思考了一下,觉得解决问题所需针对的症结不对。
因为数据准备好了之后,本身就是自动进行页面渲染的,Vue中当数据更新时视图也会自动更新,所以其实现在就是数据准备好后便自动加载的,而非手动的。
问题的症结在于,当时间设置太短的时候,没能成功完成数据的准备,然后因为js语言的特性而继续运行代码了,导致渲染时数据没准备好,所以出现显示错误的bug。而这种手动写死时间的方法又太不优雅。
下文是更加具体的分析与解决方法,是我的反馈原文。
分析与方法
我又思考了一下,对可能的问题关键做了些分析——
在该场景中的完整流程是:
- 后端,通过网络传输大量数据。
- 前端,读取网络传输的大量数据。
- 前端,将数据赋值给某数据对象。
- 前端,展示组件读取更新了的数据对象并渲染。
其中,导致出现该bug的核心环节是3,即大量数据读取与赋值的问题。
也即,在执行dataInTheWebPage = dataFromBackEnd这种赋值语句时,一方面因为该语句耗时较长,另一方面因为js语言不会等待该语句执行完就会继续执行后续语句,导致3未执行完就开始4的渲染,从而出错。
在本例中,为了避免该问题,原本设置了写死的300ms的等待时间,但在昨日运行中仍然不足,改成1000ms后能够完成赋值操作,从而没有问题。
但仍然存在两个问题:
- 不够灵活,对不同数据量通用性不足。
- 需要较长等待,用户体验不好。
在网上找到了两种方案,核心都是利用了js异步任务的特性。
方法1有较强的通用性,并且可以更快刷新页面;方法2具有很强的通用性,但刷新页面需要等完整赋值完成,用户体验可以通过前端增加加载动画来改善。
具体如下:
- 异步任务(setTimeout)+二维数组。
读取后端数据时,分割数据,循环调用setTimeout,逐次导入来更新前端数据对象,并刷新视图。
findBuyinfoBYidApi() {
findBuyinfoBYid({
id: this.RushBuyId
}).then(res => {
if (res.data.result.records && res.data.result.records.length > 0) {
首先先不将数据绑定 而是定义临时变量保存
let data = res.data.result.records; //包含大量数据的数组
调用提前封装好的方法 将n条数据分割为20份 也就是每20条数据为一个数组
let groupData = this.groupData(data,20);
最终将得出一个二位数组 里面数组又嵌套了实际的数据
for (let i = 0; i < groupData.length; i++) {
setTimeout(() => {
异步任务加入 每i*1000秒 并解构数据将数据实际传递
this.VerticalSwiper.push(...groupData[i]);
}, 1000 * i);
}
}
})
},
- 异步任务(async+await)+懒加载渲染。
读取后端数据时,通过await实现异步任务的严格顺序执行,将全部数据完整导入前段对象。
(现用的图表组件,其实已实现了对大量数据的懒加载,解决了环节4)
https://www.jb51.net/article/230214.htm (重点为参考链接中的最后一条懒加载中的onMounted方法)
onMounted(async () => {
const res = await getList()
list.value = res
})
总结
其实问题并不复杂。
只是一些js基础的语言特性,以及异步编程的方式,再加上一点对具体需求的思考与实现,就可以了。
但是在写之前版本的代码时,对js的认识,以及对异步的认识都很欠缺,导致写出来的代码存在鲁棒性低、解决方案不够优雅的问题。
当出现较为糟糕的网络环境或者不够理想的机器性能或者超出预期规模的数据时,便会出现异常,并且无法直接给用户和开发者明确的问题原因提示。
哦对,关于图表展示,记得要用高性能组件,不要使用ElementPlus里那种未经优化的基础组件,当然你自己写懒加载优化也可以。