【知识整理】多次快速请求同一数据接口时,由于请求返回的时间原因导致数据渲染混乱的web前端解决方案

问题的发现

在前端开发过程中,曾经遇到过两次bug,经过排查以后,发现产生bug的原因应该是差不多的,所以想总结一下。
具体的场景如下:

  1. 下图中搜索框是实时搜索的,当输入“2”的时候,传参是“2”,请求一次接口;当在“2”的基础上继续输入“3”时,传参是“23”,再一次请求接口。当用户快速的不停的来回输入“2”和“23”时,前端界面数据渲染会出现错乱的情况,明明输入的是“23”,但是前端界面渲染的却是输入为“2”的内容。
    搜索
  2. 下图是tab页导航,当不停的来回切换tab页的时候,前端界面数据渲染也会出现错乱的情况,即明明点击的是第二个tab导航,但是前端界面渲染的确是第一个tab页里面的内容。
    tab页导航

经过排查问题,发现引发上面两种情况的原因本质是一样的:
3. 用户快速频繁的操作,导致了多次发送同一接口不同参数的请求。
4. 后台接口由于不同参数的查询操作导致数据响应时间差异大。

如果用户没有快速操作,每次请求发送的时间有一定间隔,这时即使接口响应时间略有差异,也不会出现上述bug。如果后台数据响应时间差异很小,即使用户快速操作也不会出现上述bug。正是由于以上1、2两个原因共同作用导致了bug的出现。

解决方案

在网上搜了很多关于这种数据渲染错乱的解决方案,都没有找到合适的,最后参考了这篇博客,该博客描述的问题和我遇到的问题相似,博客地址是:https://blog.csdn.net/qq_19891827/article/details/82757250

这篇文章举了一个例子:假设页面上有button1,button2两个按钮,点击这两个按钮会请求同一个后台接口,但发送的参数不同,由于参数的不同后台操作数据库的查询语句会有一定差异,这就导致了不同的查询操作耗时可能差异巨大。再假设button1发送的参数响应的时间为2秒,button2发送的参数响应时间为1秒,用户快速先后点击了button1和button2按钮。这时会先后发送两个ajax请求,过了1秒后button2的响应数据先返回渲染到DOM上,又过了1秒后button1的响应数据返回后渲染到DOM覆盖button2的数据。而这时用户页面button2由于是后点击的,处于选中状态,用户期望查看button2对应的数据,但是最后却展示button1按钮的数据,这与用户的期望不符合,是一个严重的bug。

在进行前端开发过程中,经常会遇到需要请求同一个数据接口但不同参数的需求,这种情况下当用户通过页面操作频繁请求该接口,而接口的不同参数响应时间差异较大时,容易引发数据渲染混乱的bug。针对这种问题,有三种解决方案:

方案一:限制用户快速进行同类操作,当用户通过操作触发了一次请求后,将同类的操作按钮禁用,成功响应数据后再将按钮解除禁用。示例代码如下:

<template>
     <div>
      <button @click="sendReq" :disabled="isDisabled">button1</button>
      <button @click="sendReq" :disabled="isDisabled">button2</button>
      <p>{{content}}</p>
     </div>
   </template>
   <script>
     export default {
       data(){
         return {
           isDisabled : false,  //控制按钮可用性
           content : '' //响应数据内容
         }
       }
     }
     methods : {
       sendReq(){
         this.isDisabled = true; //禁用操作按钮
         this.$axios.get('/xxxx?id=1').then(response=>{
           this.isDisabled = false;
           this.content = response.content;
	 })
       }
     }
   </script>

这种方案优点是简单粗暴,缺点是如果数据响应时间较长时,用户体验不好。

方案二:分析用户行为会发现,用户快速操作同一接口后,只希望得到最后一次操作的数据。由此想到当用户快速操作同一接口时,只发送最后一次操作的ajax请求,即可在一定程度上解决该bug。这里我设置一个名为timer的500毫秒的一次性定时器,每次用户操作时首先清除没有执行timer定时器,然后通过timer定时器延迟500毫秒执行ajax请求,这样用户在500毫秒内执行的相同操作时,没执行的timer定时器被清理,始终只会保留最后一次操作的ajax请求,500毫秒内没有相同操作时,会发送该请求。代码如下:

<template>
 <div>
   <button @click="sendReq">button1</buttono>
   <button @click="sendReq">button2</button>
   <p>{{content}}</p>
 </div>
</template>
<script>
 export default {
  data(){
   return {
    timer : null, //定义定时器用
    content : '', //响应数据内容
   }
  }
 }
 methods : {
  sendReq(){
   clearTimerout(this.timer);  //清除没执行的timer定时器
   this.timer = setTimeout(()=>{ //定义一个timer定时器,延迟执行ajax请求
    this.$axios.get('/xxxx?id=1').then(response=>{
     this.content = response.content;
    })
   }, 500)
  }
 }
</script>

上述方案可以减少无用的ajax请求,节省性能;同时也可以在一定程度上解决上述bug,但并不能完全避免,该方案相当于设置了500毫秒的容差,可以解决后台数据响应时间差距在500毫秒以内产生的bug;当后台数据响应时间差距大与500毫秒时,仍然无法避免上述bug。在此基础上我们有了进一步的解决方案。

方案三:做一个计时器,初始值为0,每一次发送ajax请求前为计时器做一个递加操作,发送ajax请求时将该值作为参数传给后台,接收响应数据时后台将该值再返回来,我们根据前端存储的计数器的值与后端返回来的值作比较,只有二者相等时说明返回的是最后一次用户操作的数据。如二者不相等,则返回的数据不是最后一次操作的数据。至此该bug完全解决。代码如下:

<template>
 <div>
  <button @click="sendReq">button1</button>
  <button @click="sendReq">button2</button>
  <p>{{content}}</p>
 </div>
</template>
<script>
 export defalut {
  data(){
   return {
    timer : null, //定义定时器
    counter : 0, //计时器
    content : '', //响应数据内容
   }
  }
 }
 methods : {
  clearTimerout(this.timer); //清除没执行的timer定时器
  this.counter++;
  this.timer = setTimeout(()=>{//定义一个timer定时器,延迟执行ajax请求
   this.$axios.get(`/xxxx?id=1&counter=${this.counter}`).then(resonse=>{
    if(response.counter < this.counter){ //后台返回的counter字段值与前端存储的计数器值做比较,如果不相等,这说明返回的数据不是用户最后一次操作的数据。因此不渲染数据到DOM上
      return;
    }
    this.content = response.content;
   })
  }, 500)
 }
</script>

上述三种解决方案,我选择的是方案二,比较简单也基本满足修复我遇到的bug。

你可能感兴趣的:(vue.js,前端vue,数据渲染错乱,解决方案,多次频繁请求同一个接口)