可以取消jQuery延期吗?

时间:2012-07-17 21:32:59

标签: javascript jquery ajax jquery-deferred cancellation

我有一种情况,我想取消延期。延迟与ajax调用相关联。

为什么我使用延迟

我不使用$ .ajax返回的普通xhr对象。我正在使用jsonp,这意味着我无法使用HTTP状态代码进行错误处理,并且必须将它们嵌入到响应中。然后检查代码,并相应地将关联的延迟对象标记为已解决或拒绝。我有一个自定义api函数,可以帮我。

function api(options) {
  var url = settings('api') + options.url;
  var deferred = $.Deferred(function(){
    this.done(options.success);
    this.fail(options.error);
  });
  $.ajax({
    'url': url,
    'dataType':'jsonp',
    'data': (options.noAuth == true) ? options.data : $.extend(true, getAPICredentials(), options.data)
  }).success(function(jsonReturn){
    // Success
    if(hasStatus(jsonReturn, 'code', 200)) {
      deferred.resolveWith(this, [jsonReturn]);
    } 
    // Failure
    else {
      deferred.rejectWith(this, [jsonReturn]);
    }
  });

  return deferred;
}

为什么我要取消延期

有一个输入字段用作列表的过滤器,并会在键入结束后半秒钟自动更新列表。因为一次可能有两个ajax调用是未完成的,所以我需要取消之前的调用以确保它在第二个调用之后不返回并显示旧数据。

我不喜欢的解决方案

  • 我不想拒绝延迟,因为这会触发附加.fail()的处理程序。
  • 我不能忽视它,因为当ajax返回时,它会自动被标记为已解决或被拒绝。
  • 删除延迟将导致ajax调用返回时出错,并尝试将延迟标记为已解决或已拒绝。

我该怎么办?

有没有办法取消延期或删除任何附加的处理程序?

欢迎提供有关如何修复我的设计的建议,但我们会优先寻找一种方法来删除处理程序或防止它们被触发。

4 个答案:

答案 0 :(得分:14)

查看jQuery文档和代码,我没有看到任何取消jQuery延迟的方法。

相反,您可能需要在resolveWith处理程序中知道已经触发了后续的ajax调用,并且此ajax调用应该忽略其结果。你可以用一个全局递增的计数器来做到这一点。在ajax调用开始时,您递增计数器,然后将值捕获到局部变量中或将其作为属性放在ajax对象上。在resolveWith处理程序中,检查计数器是否仍具有与ajax调用开始时相同的值。如果没有,则忽略结果。如果是,则不会触发新的ajax调用,因此您可以处理结果。

或者,您可以在飞行中拒绝发出新的ajax呼叫,这样您一次就不会有多个飞行中的呼叫。完成后,您可以根据需要使用该结果或触发下一个结果。

答案 1 :(得分:6)

虽然你不能像你想要的那样“取消”延迟,你可以创建一个简单的闭包来跟踪最后一个ajax调用,通过$ .ajax返回一个jqXHR对象。通过执行此操作,您可以在新的jqXHR进入播放时简单地中止()调用,如果最后一个未完成。在你的代码的情况下,它会拒绝jqXHR并保留deferred open以便你最初想要删除。

var api = (function() {
    var jqXHR = null;

    return function(options) {
        var url = options.url;

        if (jqXHR && jqXHR.state() === 'pending') {
            //Calls any error / fail callbacks of jqXHR
            jqXHR.abort();
        }

        var deferred = $.Deferred(function() {
            this.done(options.success);
            this.fail(options.error);
        });

        jqXHR = $.ajax({
             url: url,
             data: options.toSend,
             dataType: 'jsonp'
        });

        jqXHR.done(function(data, textStatus, jqXHR) {
            if (data.f && data.f !== "false") {
                deferred.resolve();
            } else {
                deferred.reject();
            }
        });

        //http://api.jquery.com/deferred.promise/  
        //keeps deferred's state from being changed outside this scope      
        return deferred.promise();
    };
})();

我已在jsfiddle上发布此内容。如果你想测试它。设置超时与jsfiddles delayer结合使用,以模拟正在中断的呼叫。您需要启用控制台的浏览器才能查看日志。

在旁注上,将任何.success(),. error()和complete()方法切换为延迟方法done(),fail()和always()。通过jquery/ajax

  

弃用通知:jQuery 1.8中不推荐使用jqXHR.success(),jqXHR.error()和jqXHR.complete()回调。要准备最终删除的代码,请使用jqXHR.done(),jqXHR.fail()和jqXHR.always()代替更新

答案 2 :(得分:1)

JustinY:看起来你已经非常接近你想要的东西了。您已经使用了两个延迟(内部> ajax和外部 - > $ .Deferred())。然后,您使用内部延迟来决定如何根据某些条件解决外部延迟。

好吧,所以当你不想要的时候根本不解决外部延迟(也许你有一个布尔变量作为一个切换门,允许内部dfd解析/拒绝所有)。没有什么不好的事情会发生:你对这整个功能的任何处理者都不会被解雇。内部成功函数中的示例:

if(gateOpen){
  gateOpen = false;
  if(hasStatus(jsonReturn, 'code', 200)) {
    deferred.resolveWith(this, [jsonReturn]);
  }
  else {
    deferred.rejectWith(this, [jsonReturn]);
  }
}

应用程序中的其他一些逻辑将决定gateOpen何时被设置回true(某种_.throttle()或_.debounce()超时,用户交互,无论你想要什么)。如果你想跟踪或取消该函数的else中的其他请求,你也可以这样做。但基本的是你不必解决或拒绝那个外延期。这与取消它相同,即使你没有取消/中止内部。

答案 3 :(得分:1)

我创建了一个垫片,可以无缝地添加取消延迟对象和ajax请求的功能。

简而言之,一旦取消了延迟对象,就会完全忽略解析/拒绝,state会被“取消”。

根据jQuery.com,“一旦对象进入已解决或被拒绝状态,它就会保持该状态。”因此,一旦解析或拒绝延迟对象,将忽略尝试取消。

(function () {
    originals = {
        deferred: $.Deferred,
        ajax: $.ajax
    };

    $.Deferred = function () {

        var dfr = originals.deferred(),
            cancel_dfr = originals.deferred();

        dfr.canceled = false;

        return {
            cancel: function () {
                if (dfr.state() == 'pending') {
                    dfr.canceled = true;
                    cancel_dfr.resolve.apply(this, arguments);
                }
                return this;
            },

            canceled: cancel_dfr.done,

            resolve: function () {
                if ( ! dfr.canceled) {
                    dfr.resolve.apply(dfr, arguments);
                    return this;
                }
            },

            resolveWith: function () {
                if ( ! dfr.canceled) {
                    dfr.resolveWith.apply(dfr, arguments);
                    return this;
                }
            },

            reject: function () {
                if ( ! dfr.canceled) {
                    dfr.reject.apply(dfr, arguments);
                    return this;
                }
            },

            rejectWith: function () {
                if ( ! dfr.canceled) {
                    dfr.rejectWith.apply(dfr, arguments);
                    return this;
                }
            },

            notify: function () {
                if ( ! dfr.canceled) {
                    dfr.notify.apply(dfr, arguments);
                    return this;
                }
            },

            notifyWith: function () {
                if ( ! dfr.canceled) {
                    dfr.notifyWith.apply(dfr, arguments);
                    return this;
                }
            },

            state: function () {
                if (dfr.canceled) {
                    return "canceled";
                } else {
                    return dfr.state();
                }
            },

            always   : dfr.always,
            then     : dfr.then,
            promise  : dfr.promise,
            pipe     : dfr.pipe,
            done     : dfr.done,
            fail     : dfr.fail,
            progress : dfr.progress
        };
    };


    $.ajax = function () {

        var dfr = $.Deferred(),
            ajax_call = originals.ajax.apply(this, arguments)
                .done(dfr.resolve)
                .fail(dfr.reject),

            newAjax = {},

            ajax_keys = [
                "getResponseHeader",
                "getAllResponseHeaders",
                "setRequestHeader",
                "overrideMimeType",
                "statusCode",
                "abort"
            ],

            dfr_keys = [
                "always",
                "pipe",
                "progress",
                "then",
                "cancel",
                "state",
                "fail",
                "promise",
                "done",
                "canceled"
            ];

        _.forEach(ajax_keys, function (key) {
            newAjax[key] = ajax_call[key];
        });

        _.forEach(dfr_keys, function (key) {
            newAjax[key] = dfr[key];
        });

        newAjax.success = dfr.done;
        newAjax.error = dfr.fail;
        newAjax.complete = dfr.always;

        Object.defineProperty(newAjax, 'readyState', {
            enumerable: true,
            get: function () {
                return ajax_call.readyState;
            },
            set: function (val) {
                ajax_call.readyState = val;
            }
        });

        Object.defineProperty(newAjax, 'status', {
            enumerable: true,
            get: function () {
                return ajax_call.status;
            },
            set: function (val) {
                ajax_call.status = val;
            }
        });

        Object.defineProperty(newAjax, 'statusText', {
            enumerable: true,
            get: function () {
                return ajax_call.statusText;
            },
            set: function (val) {
                ajax_call.statusText = val;
            }
        });

        // canceling an ajax request should also abort the call
        newAjax.canceled(ajax_call.abort);

        return newAjax;
    };
});

添加后,您可以取消ajax电话:

var a = $.ajax({
        url: '//example.com/service/'
    });

a.cancel('the request was canceled');

// Now, any resolutions or rejections are ignored, and the network request is dropped.

..或简单的延迟对象:

var dfr = $.Deferred();

dfr
    .done(function () {
        console.log('Done!');
    })
    .fail(function () {
        console.log('Nope!');
    });

dfr.cancel(); // Now, the lines below are ignored. No console logs will appear.

dfr.resolve();
dfr.reject();