简易版竞态请求

209 阅读5分钟

最近玩三国杀碰到这样一个情况,匡扶汉室的将军们购买礼盒的时候一般会一次性买成百上千的盒子去开武将。但是在买盒子这一块,苟卡是一视同仁的,管你是骠骑大将军还是校尉小老弟。你都得等上一个购买盒子请求完成之后点击购买的操作才能生效。不跟常见的那种把按钮disabled掉,你可以一直点购买,但是上一个购买请求未完成,你的手速再快也不可能购买成功。而且还贴心的提示你手速太快了,想送钱你得先排队。

一开始我觉得苟卡也太辣鸡了,连这种连续购买还需要用这种方式来处理。

后来在自己的开发中有看到过竞态请求的相关文章,回头想想也合理,毕竟一边消耗元宝一边又要处理盒子购买,万一真有大将军手速快,10000元宝一秒钟之内购买了10几次。真有可能出现不必要的问题。这应该也算是竞态问题的一种吧。那究竟什么是竞态请求问题?

第一种情况

当新增接口前端没有限制,可以一直点的时候,尤其是这个新增接口响应时间还很长,如果没有处理的话,很容易新增多条数据。举一个不恰当的例子,大将军有十万元宝,正准备买10组盒子,每组盒子一万元宝,如果前端不做购买处理,恰好这么大将军手速又很快,一秒钟购买了十下。后端又又又又恰好每次都拿十万来做比较,这样就可能导致,大将军花一万元宝,买了10组盒子,真的太爽了。 当然这只是我的猜测。

第二种情况

当我们开发中后台列表筛选功能的时候,有时候可能出现这样一个问题。当我们下拉筛选框的时候,例如我们先筛选男然后请求数据,紧接着马上筛选女。 第一个请求突然有些延迟,第二个女筛选条件先返回数据,然后再男筛选条件返回数据。现在页面出现这种情况,页面筛选框是女,但是得到展示的数据却是男。 换句话说,当第一个筛选条件是男时,因为数据量太大导致响应时间太长。刚好你又请求了第二个女筛选条件,这个条件很快就响应了。于是女筛选条件先响应,男筛选后响应。这时候页面上数据显示的是男,但是筛选条件却是女。这不尴尬了嘛!

解决办法

  1. 常见的法子就是当发送一次请求的时候把按钮给禁止掉,不让点击。完美解决!
  2. 有些场景把按钮给禁止掉会给用户一种反馈感很差,就像文章上面购买盒子的场景。如果每次购买都把按钮置灰(禁止),用户就会有一种很不爽的感觉。不知道掘友们有没有,反正我是有,恨不得每次点击都有响应。

碰到第二种情况就需要用代码去控制了,代码如下。 下面代码实现两个功能

  • 同一个接口,且同一个参数的接口连续发送时,当上一个接口没有响应成功接下来的请求都直接被忽略掉,不会加入到发送请求的队列中。
class RaceRequest {
    requestMap = new Map();

    race(request) {
        const {
            url,
            params,
            callFunction
        } = request;
        // 如果request中对应的url不存在,就存放到requestMap中
        if(!this.requestMap.has(url)) {
            this.requestMap.set(url, {
                sameUrlRequestList: [],
                isCalled: false
            });
        }
        const list = this.requestMap.get(url).sameUrlRequestList;
        // 上一个请求和当前请求一模一样的话不触发当前请求
        if(list.length > 0 && list[list.length - 1].params === params) {
            return Promise.reject('repetition request');
        }
        this.requestMap.get(url).sameUrlRequestList.push({
            params,
            callFunction
        });
        return this.callRequest(url, false);
    }
    // isMore 表示同一个url还有接口没有调
    callRequest(url, isMore) {
        return new Promise((resolve, reject) => {
            if(!this.requestMap.get(url).isCalled || isMore) {
                this.requestMap.get(url).isCalled = true;
                const listLength = this.requestMap.get(url).sameUrlRequestList.length;
                if(listLength > 0) {
                    this.requestMap.get(url).sameUrlRequestList[listLength - 1].callFunction().then((result: any) => {
                        // 调用完这个接口之前同一个url但是入参不一样的接口又触发了,切只处理第一和最后一道
                        if(this.requestMap.get(url).sameUrlRequestList.length > listLength) {
                            return resolve(this.callRequest(url, true));
                        }else {
                            this.requestMap.delete(url);
                            resolve(result);
                        }
                    }).catch((e) => {
                        reject(e);
                    });
                }
            }
        });
    }
}
export default new RaceRequest();

使用

该demo是在vue2项目中使用。

import raceRequest from 'xxxxxxxxx';
let params = {
    sex: '男',
    age: 18,
    hobby: '唱,眺,rap,篮球'
}
raceRequest.race({
    url: 'xxx/xxx/xxx',
    params: JSON.stringify(params),
    callFunction: () => xxxxSerives(params)
})

效果如下

这里先教大家一个调试方法,因为如果接口响应很快,根本看到效果。以chrome为例,打开控制台,然后找到network面板。正常情况是No throttling 可以选择响应的网络,还可以自定义网络。

image.png

image.png

看重复调一个接口的效果,在第一个接口响应的期间发送的请求,全部会被过滤的,不会发送出去。

image.png

同一个接口不同参数,只会响应第一个和最后一个请求,并且是上一个请求完成之后才会发送下一个请求,这样就不会出现最后一个请求响应比之前的接口早的情况。

结尾

至此简易版竞态请求已经写完了,当然处理方式有很多种,可以根据不同场景写出不同竞态代码。By the way。苟卡快打钱过来,在过年这么高端的平台,用了这么多篇幅描述苟卡,高低给我封个大将军,全武将免费用。