多次请求同一接口渲染的不是最新的数据

项目场景(angular):

根据用户输入的内容对表格数据进行搜索显示

问题描述:

用户输入姓名对人员信息名单进行搜索,每输入一个字符,均会向后端进行请求,从数据库中获取符合条件的数据,当用户通过页面操作频繁请求该接口,而接口的不同参数响应时间差异较大时,易产生数据渲染混乱的问题。

说明:有一个姓名搜索框,用户可输入进行人员列表搜索,当用户快速的输入张三,前端会先调一个参数是“张”的列表接口,然后再掉一次参数是“张三”的相同列表接口,第一次调用响应时间是3秒,第二次调用响应时间是1秒,因此会先返回第二次调用的响应数据,然后再返回第一次响应数据,第一次的响应数据就会覆盖掉第二次响应数据,页面上输入框里的搜索内容是“张三”,而表格内显示的数据却是根据“张”搜索返回的数据,出现了页面的混乱


原因分析:

1、用户操作过于快速以及频繁,导致短时间内多次发送同一接口不同参数的请求
2、不同参数的查询操作导致后端接口响应时间不同


解决方案:

方案一:增加loading或禁用,适用于按钮操作

在点击按钮请求接口后,可启用loading或禁用按钮,禁止用户进行其他操作,直至响应数据返回后才可进行下步操作。

  • 设置loading
import {Component, OnInit} from '@angular/core';
@Component({
  selector: 'app-test',
  template: `
    <div [loading]="loading">
    	<button nz-button (click)="getData(1)">1</button>
    	<button nz-button (click)="getData(2)">2</button>
    	<div>{{data}}</div>
    </div>
  `,
  styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit{
	loading=false;
	data;
  constructor() {}
  ngOnInit() {}
  getData(param){
	  this.loading=true;
	  this.service.getData(param).pipe(finalize(() => {
           this.loading = false;
       })).subscribe((res) => {
           if (res.code === '2000') {
               this.data= res.data.data;
           }
       });
  }
}

  • 禁用按钮
import {Component, OnInit} from '@angular/core';
@Component({
  selector: 'app-test',
  template: `
    <div>
    	<button nz-button [disabled]="disabled" (click)="getData(1)">1</button>
    	<button nz-button [disabled]="disabled" (click)="getData(2)">2</button>
    	<div>{{data}}</div>
    </div>
  `,
  styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit{
	disabled=false;
	data;
  constructor() {}
  ngOnInit() {}
  getData(param){
	  this.disabled=true;
	  this.service.getData(param).pipe(finalize(() => {
           this.disabled= false;
       })).subscribe((res) => {
           if (res.code === '2000') {
               this.data= res.data.data;
           }
       });
  }
}

优点:简单
缺点:当响应时间越长,用户体验感越差,页面会更长时间处于无法操作的状态


方案二:利用定时器

根据用户行为可知,用户只需要最新一次操作的数据,我们只要发送最后一次请求即可。
方法:
1、设置500ms的定时器(定时时间可根据情况自定义)
2、每次用户操作此接口时先清除未执行的定时器,通过定时器延迟执行请求
结果:
用户在500ms内执行的相同操作时,未执行的定时器将被清理,始终只会保留最新的操作请求,若500ms内没有相同操作时,则会发送该请求

import {Component, OnInit} from '@angular/core';
@Component({
  selector: 'app-test',
  template: `
    <div>
    	<input nz-input [(ngModel)]="value" (ngModelChange)=getData(value)/>
    	<div>{{data}}</div>
    </div>
  `,
  styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit{
	value=null;
	timer;
	data;
  constructor() {}
  ngOnInit() {}
  getData(param){
	  if(this.timer) clearTimeout(this.timer);
	  this.timer = setTimeout(()=>{
		  this.service.getData(param).subscribe((res) => {
		          if (res.code === '2000') {
		              this.data= res.data.data;
		          }
		      });
	  },500);
  }
}

优点:减少无用请求,节省一定性能,在一定程度上解决问题
缺点:无法完全避免问题,该方案是通过设置容差,可解决数据响应时间差在500ms内的问题,但当数据响应时间差大于设置的时间,此问题仍将存在


方案三(方案二基础上优化,需后端配合)

  • 在方案二的基础上,做一个计时器,初始值为0
  • 每一次发送请求前为计时器做一个递加操作,发送请求时将该值作为参数传给后台
  • 接收响应数据时后台将该值再返回来
  • 根据前端存储的计数器的值与后端返回来的值作比较
    • 只有二者相等时说明返回的是最后一次用户操作的数据
    • 若二者不相等,则返回的数据不是最后一次操作的数据。
import {Component, OnInit} from '@angular/core';
@Component({
  selector: 'app-test',
  template: `
    <div>
    	<input nz-input [(ngModel)]="value" (ngModelChange)=getData(value)/>
    	<div>{{data}}</div>
    </div>
  `,
  styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit{
	value=null;
	count = 0;
	timer;
	data;
  constructor() {}
  ngOnInit() {}
  getData(param){
	  if(this.timer) clearTimeout(this.timer);
	  this.timer = setTimeout(()=>{
	  	const payload = {
	  		param:param,
	  		count:count++
  		};
		  this.service.getData(payload).subscribe((res) => {
		          if (res.code === '2000' && res.data.count===this.count) {
		       		this.data= res.data.data;
		          }
		      });
	  },500);
  }
}

优点:结合方案二的优点,并在此基础上解决方案二的缺点,即使响应时间差超过的设定的时间,仍可保证页面渲染的数据是最新请求的数据
缺点:需后端配合,非纯前端处理


方案四(angualr http)【angular项目推荐使用】

通过防抖来优化与服务器的交互

<input nz-input [(ngModel)]="value" (ngModelChange)="search()"/>

<ul>
  <li *ngFor="let package of packages$ | async">
    <b>{{package.name}} v.{{package.version}}</b> -
    <i>{{package.description}}</i>
  </li>
</ul>
getList(payload): Observable<any> {
        return this.http.post<any>(`/...`, payload);
    }
	value;
	detailData  = [];
	private searchText$ = new Subject<string>();
	constructor(
	        private service: TestService,
	    ) {
	    }
	ngOnInit(): void {
	        this.debounceList();
	        this.getList();
	    }

    debounceList() {
        this.searchText$.pipe(
            debounceTime(500), //  等待,直到用户停止输入(500ms)
            distinctUntilChanged(), //  等待,直到搜索内容发生了变化
            switchMap(obj =>
                this.service.getList(obj)) // 把搜索请求发送给服务
        ).subscribe(res => {
            if (res.code === '2000') {
                this.loading = false;
                this.detailData = res.data.datalist;
            } else {
                this.loading = false;
            }
        }, error => {
            this.loading = false;
        });
    }
    
    getList(reset = false) {
        this.loading = true;
        const payload = {
            value:this.value
        };
        this.searchText$.next(payload);
    }

searchText$ 是一个序列,包含用户输入到搜索框中的所有值。 它定义成了 RxJS 的 Subject 对象,这表示它是一个多播 Observable,同时还可以自行调用 next(value) 来产生值。
除了把每个 searchText 的值都直接转发给 Service 之外,ngOnInit() 中的代码还通过下列三个操作符对这些搜索值进行管道处理,以便只有当它是一个新值并且用户已经停止输入时,要搜索的值才会抵达该服务。

  • debounceTime(500)⁠—等待用户停止输入(500毫秒)。
  • distinctUntilChanged()⁠—等待搜索文本发生变化。
  • switchMap()⁠—将搜索请求发送到服务。

switchMap() 操作符有三个重要的特征:

  • 其参数是一个返回 Observable 的函数,service.getList会返回 Observable
  • 若以前的搜索结果仍然是在途状态,会取消那个请求,并发起一次新的搜索。
  • 会按照原始的请求顺序返回这些服务的响应,而不用关心服务器实际上是以乱序返回的请求
优点:减少无用请求,节省性能,能很好的解决当前问题,无需后端配合,不用修改原本接口
缺点:仅适用于angular项目


方案五(未实际操作过)

网上查找大量资料,大多数都提到了axios 中的一个属性‘cancelToken’
可参考

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值