Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
VaJoy opened this issue Dec 9, 2015 · 9 comments
Open

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

VaJoy opened this issue Dec 9, 2015 · 9 comments

Comments

@VaJoy
Copy link
Member

VaJoy commented Dec 9, 2015

现在有一个按钮 $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
Copy link

            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
Copy link

inJs commented Dec 10, 2015

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

/**
 * 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
Copy link
Member Author

VaJoy commented Dec 10, 2015

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

@kaleys
Copy link

kaleys commented Dec 10, 2015

//还差一步,就是$.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
Copy link

@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
Copy link
Member Author

VaJoy commented Dec 10, 2015

@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
Copy link
Member

tudousi commented Dec 10, 2015

<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
Copy link

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

    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
Copy link
Member Author

VaJoy commented Dec 11, 2015

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants