javascript jQuery 延迟可以取消吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11531184/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 13:33:44  来源:igfitidea点击:

Can jQuery deferreds be cancelled?

javascriptjqueryajaxjquery-deferredcancellation

提问by

I have a situation where I want to cancel a deferred. The deferred is associated with an ajax call.

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

Why I am using deferreds

为什么我使用延迟

I don't use the normal xhr objects returned by $.ajax. I'm using jsonp, which means I can't use HTTP status codes for error handling and have to embed them in the responses. The codes are then examined and an associated deferred object is marked as resolved or rejected accordingly. I have a custom api function that does this for me.

我不使用 $.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;
}

Why I want to cancel the deferred

为什么我要取消延期

There is an input field that serves as a filter for a list and will automatically update the list half a second after typing ends. Because it is possible for two ajax calls to be outstanding at a time, I need to cancel the previous call to make sure that it doesn't return after the second and show old data.

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

Solutions I don't like

我不喜欢的解决方案

  • I don't want to reject the deferred because that will fire handlers attached with .fail().
  • I can't ignore it because it will automatically be marked as resolved or rejected when the ajax returns.
  • Deleting the deferred will cause an error when the ajax call returns and tries to mark the deferred as resolved or rejected.
  • 我不想拒绝 deferred ,因为这会触发带有.fail().
  • 我不能忽略它,因为它会在 ajax 返回时自动标记为已解决或已拒绝。
  • 当ajax调用返回并尝试将延迟标记为已解决或被拒绝时,删除延迟会导致错误。

What should I do?

我该怎么办?

Is there a way to cancel the deferred or remove any attached handlers?

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

Advice on how to fix my design is welcome, but preference will be given to finding a way to remove handlers or prevent them from firing.

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

采纳答案by jfriend00

Looking in the jQuery doc and code, I don't see any way to cancel a jQuery deferred.

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

Instead, you probably need a way in your resolveWithhandler to know that a subsequent ajax call has already been fired and this ajax call should ignore its result. You could do that with a globally incrementing counter. At the start of your ajax call, you increment the counter and then you grab the value into a local variable or put it as a property on the ajax object. In your resolveWithhandler, you check to see if the counter still has the same value as when your ajax call started. If not, you ignore the result. If it does, no new ajax calls have been fired so you can process the result.

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

Alternately, you could refuse to fire a new ajax call while one is in flight already so you never had more than one in flight at a time. When the one finishes, you could either just use that result or fire the next one if desired.

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

回答by Ryan Q

While you can't "cancel" a deferred like you want, you could create a simple closure to keep track of the last ajax call through $.ajax returning an jqXHR object. By doing this you can simply abort() the call when a new jqXHR comes in to play if the last one wasn't finished. In your code's case it will reject the jqXHR and leave the deferred open to be deleted as you initially wanted.

虽然您不能像您想要的那样“取消”延迟,但您可以创建一个简单的闭包来跟踪最后一次 ajax 调用,通过 $.ajax 返回一个 jqXHR 对象。通过这样做,如果最后一个未完成,您可以在新的 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();
    };
})();

I've posted this on jsfiddle. If you wish to test it out. Set timeout is used in combination with jsfiddles delayer to simulate a call being interupted. You'll need a console enabled browser to see the logs.

我已经在jsfiddle上发布了这个。如果你想测试一下。设置超时与 jsfiddles 延迟器结合使用来模拟中断的呼叫。您需要一个支持控制台的浏览器来查看日志。

On a side note switch any .success(), .error(), and complete() methods over to deferred methods done(), fail(), and always(). Via jquery/ajax

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

Deprecation Notice: The jqXHR.success(), jqXHR.error(), and jqXHR.complete() callbacks will be deprecated in jQuery 1.8. To prepare your code for their eventual removal, use jqXHR.done(), jqXHR.fail(), and jqXHR.always() instead as newer

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

回答by Dtipson

JustinY: seems like you're really close already to what you want. You're already using two deferreds (inner- > the ajax and outer -> $.Deferred()). You're then using the inner deferred to decide how to resolve the outer deferred based on some conditions.

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

Well, so just don't resolve the outer deferred at all when you don't want to (maybe you have a boolean variable that serves as a toggle gate for allowing the inner dfd to resolve/reject at all). Nothing bad will happen: whatever handlers you have attached to this entire function won't fire. Example in your inner success function:

好吧,所以当你不想的时候根本不要解决外部延迟(也许你有一个布尔变量作为一个切换门,允许内部 dfd 解决/拒绝)。不会发生任何坏事:您附加到整个函数的任何处理程序都不会触发。内部成功函数中的示例:

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

Some other logic in the application will decide when the gateOpen gets set back to true (some sort of _.throttle() or _.debounce() timeout, user interaction, whatever you want).If you wanted to track or cancel other requests in the else of that function, you could do that too. But the basic thing is that you don't have to resolve OR reject that outer deferred. And that's the same as canceling it, even if you don't cancel/abort the inner one.

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

回答by Joshua Hansen

I've created a shim that seamlessly adds the ability to cancel deferred objects and ajax requests.

我创建了一个 shim,它无缝地添加了取消延迟对象和 ajax 请求的能力。

In short, once a deferred object has been canceled, resolutions/rejections are completely ignored, and the statebecomes "canceled".

简而言之,一旦推迟的对象被取消,决议/拒绝将被完全忽略,并且state变为“取消”。

According to jQuery.com, "Once the object has entered the resolved or rejected state, it stays in that state." Therefore, attempts to cancel are ignored once a deferred object is resolved or rejected.

根据 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;
    };
});

Once added, you may cancel an ajax call:

添加后,您可以取消 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.

..or a simple deferred object:

..或一个简单的延迟对象:

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();