如何将所有处理程序从一个延迟转移到另一个处理程序?

时间:2013-10-31 17:18:37

标签: jquery promise jquery-deferred

假设我有一个$.Deferred和一个jqXHR对象。有没有办法将绑定到延迟(然后,总是,完成,失败)的所有处理程序转移到XHR对象(根据我的理解,它是Deferred的扩展)?


这就是我的想法:

$.ajaxOne = function(options) {
    var xhr = null;
    return function() {
        if(xhr) xhr.abort();
        xhr = $.ajax(options).always(function() {
            xhr = null;
        });
    }
}

我想创建一个类似于$.ajax的函数,除非你快速连续多次调用它,它将中止最后一个请求并只完成最新的请求。这在您要验证用户输入的许多场景中非常有用。

例如,您可能想要检查是否已经使用了用户名,但是如果他们在您启动ajax调用后再次在用户名字段中输入,则您不关心最后的结果,只关注最近的结果一。

此外,我认为请求不会保证以相同的顺序返回(我想这取决于您的服务器设置),因此您也可能遇到同步问题。

无论如何,上面代码的问题在于,因为它返回一个函数,你可以随时执行你的ajax调用,但是你不能将你的完成处理程序绑定到它。所以我必须以某种方式混合延迟处理程序并将它们重新绑定到XHR对象。

3 个答案:

答案 0 :(得分:2)

  

我们说我有一个$ .Deferred和一个jqXHR对象。有没有办法将绑定到延迟(然后,总是,完成,失败)的所有处理程序转移到XHR对象(根据我的理解,它是Deferred的扩展)?

或多或少,是的,但不是你想象的方式。而不是"移动处理程序",您只需解决延迟的XHR延迟(具有处理程序)。这将使延迟采用ajax promise的状态 - 或者不是,因为jQuery不是Promise A+兼容的。因此,您需要手动将触发器作为处理程序:

var deferred = $.Deferred(),
    xhr = $.ajax(…);
xhr.done(deferred.resolve).fail(deferred.reject).progress(deferred.notify);

但是,不建议使用此类用途,只需在需要xhr的任何地方使用deferred - 它们就是平等的。或者使用xhr.then()创建一个全新的承诺对象,其解析方式与xhr完全相同。

  

无论如何,上面代码的问题在于,因为它返回一个函数,你可以随时执行你的ajax调用,但是你不能将你的完成处理程序绑定到它。所以我必须以某种方式混合延迟处理程序并将它们重新绑定到XHR对象。

您仍然可以从返回的函数返回每个xhr对象,并将处理程序绑定到该对象。如果它被中止,将调用其error个处理程序。

$.ajaxOne = function(options) {
    var xhr = null;
    return function(name) {
        options.data = name;
        if (xhr) xhr.abort();
        return xhr = $.ajax(options).always(function() {
//      ^^^^^^
            xhr = null;
        });
    }
}
var checkUserAccount = $.ajaxOne({…});
$input.keyup(function(e) {
    checkUser(this.value).done(function(ajaxResult) {
        // do anything here with the ajaxResult from the latest call
        // if there was another keyup event, this callback never fires
    });
});
  

另外,我不认为请求会保证按照他们发出的顺序返回(我想这取决于您的服务器设置),因此您也可能遇到同步问题。

如果在再次调用该函数时在每个旧函数上调用abort,则不会 - 这将保留一次最多只有一个活动的ajax请求的不变量。

  

我想创建一个类似于$ .ajax的函数,除非你快速连续多次调用它,它将中止最后一个请求并且只完成最近的请求。

听起来非常像一个事件流。您将需要查看功能反应式编程!

答案 1 :(得分:0)

想出了这个:

function AjaxOne(options) {
    this.options = options;
    this._xhr = null;
    this._always = [];
    this._success = [];
    this._fail = [];
};

$.extend(AjaxOne.prototype, {
    always: function(cb) {
        this._always.push(cb);
        return this;
    },
    done: function(cb) {
        this._success.push(cb);
        return this;
    },
    fail: function(cb) {
        this._fail.push(cb);
        return this;
    },
    then: function(success, fail) {
        this._success.push(success);
        this._fail.push(fail);
        return this;
    },
    run: function(options) {
        if(this._xhr) {
            this._xhr.abort();
        }
        this._xhr = $.ajax($.extend({},options,this.options,{context:this}))
            .always(function() {
                this._xhr = null;
                for(var i=0; i<this._always.length;++i) this._always[i].apply(this,arguments);
            })
            .done(function() {
                for(var i=0; i<this._success.length;++i) this._success[i].apply(this,arguments);
            })
            .fail(function() {
                for(var i=0; i<this._fail.length;++i) this._fail[i].apply(this,arguments);
            });
    }
});

到目前为止似乎工作得很好......但它没有回答我原来的问题。

所以一个全面的答案是:你不能将回调从一个延迟复制到另一个。我尝试以各种方式制作延期副本,$.extend({}, myDeferred)我无法解决任何问题。我认为你必须手动制作每种方法的副本并启动相应的回调,类似于我所做的。

你可以将回调传递给原来的延期,正如Arun在他的评论中所暗示的那样(尽管我认为他的语法有些偏差;根据the docs,你不需要.apply; 'with'方法的目的是允许您设置上下文)。在我的情况下,我希望能够不止一次地触发这些方法,因此这对我的方案不起作用。

答案 2 :(得分:0)

Fiddle here.

我已经实现了不使用延迟对象。

keyup事件 -

$("#uID").keyup(function () {
    console.log("KeyUp")
    var textElement = this;
    clearTimeout(textElement.timer);
    if(textElement.xhrReq && textElement.xhrReq.abort){
        textElement.xhrReq.abort();
    }
    textElement.timer = setTimeout(function(){
        console.log("Invoking validation : " + textElement.value);
        validateUID(textElement);
    }, 1000);
});

在validateUID()中,我已将整个XHR对象分配给输入元素的属性 -

textElement.xhrReq = $.ajax({
...
});

一个缺点 - 我承认它在那里 - 我们需要将整个XHR对象保留在元素中。