防止重复发送ajax请求的初步探究

防止重复发送ajax请求的初步探究

@(我的第一个笔记本)[孙传博]


问题再现

Alt text
在写看点账号和关键词监控的微博文章部分时,有一个下拉加载的功能,基本实现后在测试时发现:每当下拉的过快或者做重复下拉的操作,ajax就会请求不到数据并返回canceled的状态码。


原因探究

经过排查,ajax请求被canceled是因为在ajax请求资源的时间内,后续的操作继续触发了ajax请求资源,因此请求操作被取消,所以解决问题关键就是如何防止重复发送ajax请求。


解决方法
  • 1 节制型提交(throttle)
    无论提交如何频繁,任意两次有效提交的间隔时间必定会大于或等于某一时间间隔;即以一定频率提交。
module.submit = throttle(150, function() {
  // todo
})

throttle释义:

throttle在英文中是节流阀的意思,顾名思义,就像原本一直流水的龙头,现在每隔一定时间开一下,以节流。也就是会说预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。

使用场景:
鼠标移动,mousemove 事件;DOM 元素动态定位,window对象的resize和scroll 事件等。

demo:

    window.addEventListener("resize", throttle(callback, 3000, {leading:true}));
    window.addEventListener("resize", throttle(callback2, 0, {leading:false}));
    function callback ()  { console.count("Throttled"); }
    function callback2 () { console.count("Not Throttled"); }
    /**
    * 频率控制函数, fn执行次数不超过 1 次/delay
    * @param fn{Function}     传入的函数
    * @param delay{Number}    时间间隔
    * @param options{Object}  如果想忽略开始边界上的调用则传入 {leading:true},
    *                         如果想忽略结束边界上的调用则传入 {leading:false},
    * @returns {Function}     返回调用函数
    */
    function throttle(fn,delay,options) {
        var wait=false;
        if (!options) options = {};
        return function(){
            var that = this,args=arguments;
             if(!wait){
                 if (options.leading === false){
                     // 非截流操作
                     fn.apply(that,args)
                    }
                 else { 
                    // 截流操作
                    wait=true;
                    setTimeout(function () {
                       fn.apply(that,args);
                       wait=false;
                    },delay);
                }
            }
        }
    }
  • 2 懒惰型提交(debounce)
    任意两次提交的间隔时间,必须大于一个指定时间,才会成功提交;
module.submit = debounce(150, function() {
  // todo
})

debounce释义:

bounce是名词弹力,或者动词反弹的意思, de-表否定/相反的前缀,形象低表示一直持续低压抑住不让反弹,当然最后还是得松手,那么反弹一次。 也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。

使用场景:
文本输入keydown 事件,keyup 事件等。

demo:

    var resizeTimer=null;
    $(window).on('resize',function(){
           if(resizeTimer){
               clearTimeout(resizeTimer)
           }
           resizeTimer=setTimeout(function(){
               console.log("window resize");
           },1000);
       }
    );

throttle和debounce的区别理解

福兮,祸之所倚;祸兮,福之所伏。
——《老子》

二者的不同之处:
throttle 可以想象成阀门一样定时打开来调节流量。 debounce可以想象成把很多事件压缩组合成了一个事件。

有个生动的类比,假设你在乘电梯,每次进一个人需等待10秒钟,不考虑电梯容量限制,那么两种不同的策略是:

debounce 你在进入电梯后发现这时不远处走来了一个人,等10秒钟,这个人进电梯后不远处又有个妹纸姗姗来迟,怎么办,再等10秒,于是妹纸上电梯时又来了一对好基友…,作为感动中国好码农,你要每进一个人就等10秒,直到没有人进来,10秒超时,电梯开动。

throttle 电梯很忙,每次就只等10秒,不管是来了妹纸还是好基友,电梯每隔10秒准时送一波人。

因此,简单来说,debounce适合只执行一次的情况,例如 搜索框中的自动完成。在停止输入后提交一次ajax请求;
而throttle适合指定每隔一定时间间隔内执行不超过一次的情况,例如拖动滚动条,移动鼠标的事件处理等。

旁礴万物以为一。
——《逍遥游》

二者的相同在于:

首先我们的直观感觉是使用 debounce 方法相比于 throttle 方法事件触发的频率更低,但实际上不能这么理解。首先需要了解 debounce 和 throttle 的原理。

当我们阅读lodashunderscore等js工具库对这两种方法的代码实现时会发现,throttle 方法不过是 debounce 方法的一个修饰。也就是说,_.throttle_.debounce最终都会都会调用 debounce 方法。那么 debounce 究竟是如何工作的呢?
首先看 _.debounce 的 API:

_.debounce(func, delay, options);

func 是需要被调用的目标函数,delay 是时间限制,options 是一系列的配置,为了简化问题,这里不多提。

当调用 _.debounce 后,会返回一个函数,这个函数在被调用时会生成一个 setTimeout(delayed, delay)。其中 delayed 又是一个内部方法,在 delayed 被调用时进行如下检测:当前时间 - 上次func被调用事件 是否 小于 0 或 大于 delay ?如果是则执行一次 func,记录并返回执行结果,同时更新上次被调用时间;如果不是则调用 setTimeout 进行下一次的判断。

因此,当你像下面这样绑定事件,

$(window).on('mousemove', _.debounce(moveHandler, 500));

并频繁移动鼠标时,你会发现 moveHandler 压根没有被调用过!直到你停下来后,moveHandler 才会被调用一次。

至于_.throttle方法,只不过是多给 debounce 传了一个 maxWait 选项,这个选项的意思是至少保证在每 maxWait 时间让 func 被调用一次。

说到这儿,我们就明白了为什么会出现上图那样的调用情况。如果你频繁的移动鼠标,throttle 会保证在每 maxWait 时间调用 func 一次,而 debounce 如果没有明确设置 maxWait,是一直不会调用 func 直到你停止移动鼠标后才会调用一次。

因此,可以笼统的说_.throttle就是设置了 leading=true, trailing=true 及 maxWait 的 _.debounce

——————————————————————————————————————————2016年11月23日


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值