41

我有一种情况,我想取消延期。deferred 与 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 调用返回并尝试将 deferred 标记为已解决或已拒绝时,删除 deferred 将导致错误。

我该怎么办?

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

欢迎就如何修复我的设计提出建议,但将优先考虑找到删除处理程序或防止它们触发的方法。

4

4 回答 4

17

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

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

或者,您可以拒绝在一个已经在飞行中的新 ajax 调用,这样您一次不会有超过一个在飞行中。当一个完成时,您可以只使用该结果,也可以根据需要触发下一个。

于 2012-07-17T21:56:57.083 回答
7

虽然您不能像您想要的那样“取消”延迟,但您可以创建一个简单的闭包来通过 $.ajax 返回一个 jqXHR 对象来跟踪最后一次 ajax 调用。通过这样做,如果最后一个没有完成,当一个新的 jqXHR 进入播放时,您可以简单地 abort() 调用。在您的代码的情况下,它将拒绝 jqXHR 并保留延迟打开以按照您最初的意愿被删除。

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 延迟器结合使用,以模拟被中断的呼叫。您需要启用控制台的浏览器才能查看日志。

附带说明一下,将任何 .success()、.error() 和 complete() 方法切换到延迟方法 done()、fail() 和 always()。通过jquery/ajax

弃用通知:jqXHR.success()、jqXHR.error() 和 jqXHR.complete() 回调将在 jQuery 1.8 中弃用。要为最终删除准备您的代码,请使用 jqXHR.done()、jqXHR.fail() 和 jqXHR.always() 作为更新版本

于 2012-08-08T10:58:45.757 回答
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 中,您也可以这样做。但基本的事情是你不必解决或拒绝外部延迟。这与取消它是一样的,即使你不取消/中止内部的。

于 2014-05-22T13:39:08.667 回答
1

我创建了一个 shim,它无缝地添加了取消延迟对象和 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();
于 2016-08-02T23:36:49.603 回答