N-ZOO / everycode

前端每日一练
163 stars 18 forks source link

[js] Ajax请求防抖实现 #2

Open VaJoy opened 8 years ago

VaJoy commented 8 years ago

现在有一个按钮 $btn,我们给它绑定了点击事件,让它点一次就发一次POST请求去后端提交统计数据:

    var $btn = $('#btn');
    $btn.click(function(){
        $.ajax({ url: "a.php", type: 'POST', success: function(){
            console.log(ret);
        }});
    });

但是如果用户在很短的时间内点了好几下按钮(比如在1s里快速地点了5下),会向服务器发送很多条数据,这样并发量大的情况下服务器可能吃不消。 而且有时候我们也希望当前一条请求结束了(服务器返回数据了)之后才可以发起第二条请求。

所以请你封装一个叫 $.smartAjax 方法来做防抖处理,它可以有一个参数smartType用于确定防抖方式——如果其值为 0 则要求在 500ms 内只允许发出一条请求; 如果参数smarType值为 1 则表示只允许在前一条收到服务器响应(包括失败的响应)后才可以继续发送新请求。

示例:

    var $btn1 = $('#btn1');
    var $btn2 = $('#btn2');

    $btn1.click(function(){
        //每500ms最多只会发出一条请求
        $.smartAjax({ smartType: 0, url: "a.php", type: 'POST', success: function(){
            console.log(ret);
        }});
    });
    $btn2.click(function(){
        //只有当前一条请求收到服务器响应了,才能再发出新的请求
        $.smartAjax({ smartType: 1, url: "a.php", type: 'POST', success: function(){
            console.log(ret);
        }});
    });

    $.smartAjax = ....   //实现它
gaozejie commented 8 years ago
            var flag = 0;
            $.smartAjax = function (args) {
                var fn = arguments.callee;
                $.ajax({
                    url: args.url,
                    data: { },
                    type: args.type,
                    dataType: "text",
                    beforeSend: function () {
                        flag += 1;
                        // 已请求过
                        if (flag > 1) {
                            if (args.smartType == 0) {
                                flag = 0;
                                //setTimeout(alert("aaa"), 10000); 这里的setTimeout无论这只多长时间都是立即执行。不知为什么。
                                (function () {
                                    var i = 0;
                                    var interval = setInterval(function () {
                                        console.log(i);
                                        i++;
                                        if (i == 5) { clearInterval(interval); fn(args); };
                                    }, 100);
                                })();
                                return false;

                            } else if (args.smartType == 1) {
                                alert("不能发请求。");
                                return false;
                            }
                        }
                    },
                    success: function (result) {
                        args.success(result);
                        flag = 0;
                    },
                    error: function () {
                        flag = 0;
                    }
                });
            };

只能算是最基本的实现,还有无限优化的空间。

inJs commented 8 years ago

为扩充提供无限可能,两个方案,两种实现方式。大家有优化点可以给出建议~!!

/**
 * Created by 二狗 on 2015/12/10.
 */

var protectiveStrategy = {
    timer : function() {
        var url = [].pop.call(arguments),
            ms = +([].pop.call(arguments)),
            self = arguments.callee,
            now = new Date().getTime();

        ms = (typeof ms === 'number' && ms <= 2000) ? ms : 500;

        if(!self.preTime || (now-self.preTime >= ms)) {
            self.preTime = now;
            $.ajax({
                url : url,
                //...
                success : function() {

                }
            });
        }
    },
    responsed : (function () {
        var canSend = true,
            url = [].pop.call(arguments);

        return function() {
            if(canSend) {
                canSend = false;
                $.ajax({
                    url : url,
                    //...
                    success : function() {

                        canSend = true;
                    }
                });
            }
        }
    }())
};

/**
 *
 * @param {String} plan 计划用何种方式代理,可选择的值为策略对象的key
 * @param {String} url 请求的URL
 */
var ajaxProxy = function(plan ,url) {
    var ary = plan.split(':'),
        strategy = ary.shift();

        ary.push(url);

    protectiveStrategy[strategy].apply(null ,ary);
};

$.smartAjax = ajaxProxy;

function onClick() {

    $.smartAjax('timer:1000' ,'localhost:8080');
    //$.smartAjax('responsed' ,'localhost:8080');
}
VaJoy commented 8 years ago

不要有全局变量污染,尽量都写在smartAjax方法里面为佳哦

kaleys commented 8 years ago
//还差一步,就是$.smartAjax得到的方法不能在方法体内保存,要先用变量保存起来,智商不够
$.smartAjax = function(opts) {
        var that = this,
            proxyFn = function(type,options,context) {
                var resFn;
                if(type===0) {
                    resFn = (function(){
                        var _first = true,
                        startTime = +new Date();
                        return function(){
                            if(_first) {
                                _first = false;
                                $.ajax(options);
                                return;
                            }
                            var now = +new Date();
                            if(now - startTime < 500 ){
                                return;
                            }
                            startTime = now;
                            $.ajax(options);
                        }
                    })()
                }else {
                    resFn = (function(){
                        var _completed = true,
                            completeFn = options.complete;
                        options.complete = function(){
                            completeFn&&completeFn();
                            console.log('完成');
                            _completed = true;
                        }
                        return function(){
                            if(_completed) {
                                _completed = false;
                                $.ajax(options);
                            }
                        }

                    })()
                }
                return resFn;
            },
            smartType = opts.smartType||0;
            delete opts.smartType;

        var resFn = proxyFn(smartType,opts);
        return resFn;
    }

    //测试代码
        //要先用after500保存下
    var after500 = $.smartAjax({
        smartType:0,
        url:'http://localhost/test.php',
        success:function(response){console.log(response)}
    });

    /*var complete = $.smartAjax({
        smartType:1,
        url:'http://localhost/test.php',
        success:function(response){console.log(response)}
    });*/

    var a = 1;
    var timer = setInterval(function(){
        if(a==20) {
            clearInterval(timer);
        }
        a++;
        after500()
        //complete();

    },200)
LittleBearBond commented 8 years ago

@VaJoy 还差个return $.ajax(options); 你那样写就没法 $.smartAjax().done().error()xxx了 看到公司有人写的,改过来大概是这样,不过占用了beforeSend,不爽。

;(function() {
    var limitPendingRequests = {};
    $.ajaxSetup({
        // 不缓存get
        cache: false,
        beforeSend: function(xhr, settings) {
            // ajax 请求限制
            var self = this;
            var port = (function() {
                var url = self.url;
                var index = url.indexOf('_');
                return index < 0 ? url : url.substring(0, index - 1)
            }());

            if (typeof settings.smartType === 'undefined') {
                return true;
            }
            //500 毫秒下一次
            if (settings.smartType === 0) {
                if (limitPendingRequests[port]) {
                    return false;
                }
                limitPendingRequests[port] = true;
                setTimeout(function() {
                    delete limitPendingRequests[port];
                }, 500);
                return true;
            }

            //请求完成进行下一次
            if (settings.smartType === 1) {
                if (limitPendingRequests[port]) {
                    return false;
                }
                limitPendingRequests[port] = true;
                xhr.complete(function() {
                    delete limitPendingRequests[port];
                });
                return true;
            }
            return true;
        }
    });
})();

/*//
$.ajax({
    smartType:0// 1,
    xxxx
})*/
//这样做会触发ajaxError 事件,不过可以根据事件参数来判断是不是主动给取消了,参数有个值是canceled
VaJoy commented 8 years ago

@LittleBearBond 你貌似艾特错人了吧?我没提交过代码,如果要我写我会这么写:

    $.smartAjax = (function () {
      var _time_start = 0,
          _flag = 1; //用于判断是否可以发送请求

      return function (opt) {
        var noop = function () {},
            validFun = function (f) {
              return typeof f === 'function' ? f : null
            },
            smartType = opt && opt.smartType,
            processResponse = function (callback) {
              ['success', 'error'].forEach(function (resFunName) {
                var resFun = validFun(opt[resFunName]) || noop;
                opt[resFunName] = function () {
                  callback();
                  resFun.call(this, arguments)
                }
              });
            },
            typeCallback = {
              0: function () {
                _flag && (_time_start = Date.now());
                if(_flag==0 && Date.now() - _time_start > 300) {
                  _flag = 1;
                  _time_start = Date.now()
                }
              },
              1: function () {
                if(_time_start) { //防止先调用smartType=0再调用smartType=1会失效
                  _flag = 1;
                  _time_start = 0
                }
                processResponse(function(){
                  _flag = 1;
                })
              }
            };
        var hasSmartType = typeCallback[smartType];
        hasSmartType && typeCallback[smartType]();
        if(_flag) {
          hasSmartType && (_flag = 0);
          return $.ajax(opt);
        } else {
          return $.Deferred()
        }
      }
    })();

不存在外部变量,也不用占用什么beforeSend,也可以正常使用 $.smartAjax({...}).done(...)

tudousi commented 8 years ago
<button id="req1">
    请求 smartType:1
</button>
<button id="req2">
    请求 smartType:2
</button>
/*
smartType:1  => 500ms 只能发送一次
smartType:2  => 等待请求返回后才能再次发起
*/
$.smartAjax = function(args) {
    var send = function(opts) {
        $.ajax(opts);
    };
    var afterSuccess = args.success;
    var afterError = args.error;
    return function() {
        var exec = [
            function() {
                if (!send) return;
                var oldSend = send;
                send(args);
                send = null;
                setTimeout(function() {
                    send = oldSend;
                }, 500);
            },
            function() {
                if (!send) return;
                var oldSend = send;
                args.success = function(result) {
                    send = oldSend;
                    afterSuccess(result);
                };
                args.error = function(result) {
                    send = oldSend;
                    afterError(result);
                };
                send(args);
                send = null;
            }
        ];
        exec[args.smartType - 1]();
    };
};

var send1 = $.smartAjax({
    smartType: 1,
    url: 'http://sandbox.runjs.cn/show/teu1xt1n'
})
var send2 = $.smartAjax({
    smartType: 2,
    url: 'http://www.google.com',
    success: function(data) {
        //console.log(data);
        console.log('data')
    }
})
$('#req1').click(function() {
    send1();
});
$('#req2').click(function() {
    send2();
});
wanglianjie91 commented 8 years ago

想破头了,依然没什么头绪。坐等最佳实践。

    var queue = {             //创建队列,点击按钮将请求添加到队列,然后按照固定时间释放
        queStore : [],
        length : 0,
        timeout : 2000,
        timer : null,

        add : function(arg){
            this.queStore.push(arg);
            this.length++;
            return this;
        },
        remove : function(i){
            this.queStore.splice(i,1);
            this.length--;
            return this;
        },
        dequeue : function(){
            if(queue.length>0){
                var a = queue.queStore.shift();
                queue.length--;
                a();
            }else{
                clearInterval(this.timer);
            }               
        },
        fire : function(){
            clearInterval(this.timer);
            this.timer = window.setInterval(function(){queue.dequeue.call(queue);},this.timeout);
        }       
    };

    var flag = true;

    $.smartAjax = function(arg){
        var def = {
            smartType:0,
            url:"",
            type:"POST",
            success:null
        };
        var ajaxFun = function(){
            $.ajax({
                    url:opation.url,
                    type:opation.type,
                    success:opation.success
                });
        };

        var opation = $.extend(def,arg);

        queue.add(ajaxFun);

        switch(opation.smartType){
            case 0 :            
            queue.fire();
            break;
            case 1 :
            //另外一套逻辑。
            break;
            default:
            throw new Error("参数类型错误!");

        }
    }
VaJoy commented 8 years ago

@qianduanMortal 其实不用搞队列,防抖的初衷本来就是忽略冗余的请求 没有什么最优解,只有参照的,可以参照我在上面发的代码