jquery ajax: 以正确的顺序处理预输入事件?

jquery ajax: process typeahead events in correct order?

我正在做一个带有 html+jquery 和 java 休息服务后端的网络应用程序。 我有一个带有预输入建议的文本字段,因此用户在该字段中输入的每个字符 将触发服务器往返并更新提前输入建议列表。

代码的基本部分:

    var showTypeaheadSuggestions = function(data) {
        // update ui-element ...
    }

    var displayFailure = function() {
        // update ui-element ...
    }

    var searchText = $("#searchText");
    var searchTextKeyup = function() {
        var txt = searchText.val();
        $.ajax({
            url : typeaheadUrl(txt),
            type : 'GET',
            dataType : 'json',
        }).done(showTypeaheadSuggestions).fail(displayFailure);
    }
    searchText.on('keyup', searchTextKeyup);

基本上可以用了。 但是我在想,如果你键入 2 个字母 "ab" 会发生什么(这将首先触发对 "a" 的请求,然后是对 "ab" 的请求)...

那么,如果 "a" 响应的处理时间稍长,并且在 "ab" 响应 之后 到达,会发生什么情况? 我是否需要在我的代码中 检测 以丢弃 "a" 响应?

http://api.jquery.com/jquery.ajax/ 中确实说:

Promise callbacks — .done(), .fail(), .always(), and .then() — are invoked, in the order they are registered.

这到底是什么意思? 我希望这意味着 $.ajax() 会自动正确处理上述情况。

但是当我做一个小测试时(在服务器端我只是注入了 2 秒的睡眠延迟,只有当搜索字符串正好是 "a" 时), 事实证明它确实没有表现得像我预期的那样。

预先输入列表将首先使用 "ab" 响应进行更新,然后在 "a" 响应时进行更新 到了,它也会更新,所以提前输入列表得到了错误的建议。

正确处理此问题的既定方法是什么?

我解决这个问题的方法之一是为每次调用分配一个 ID,并将它作为 ID 传递给服务器端。当您的服务器完成处理后,它会将结果连同它的 ID 一起发回。

然后,每次客户端代码运行时,ID 都会递增。例如:

var callback_id = 0;

var searchText = $("#searchText");
var searchTextKeyup = function() {
    callback_id ++;
    var txt = searchText.val();
    $.ajax({
        url : typeaheadUrl(txt),
        data : callback_id,
        type : 'GET',
        dataType : 'json',
    }).done(showTypeaheadSuggestions).fail(displayFailure);
}
searchText.on('keyup', searchTextKeyup);

然后,当您收到返回的响应时,您检查该 ID 是否是当前 ID。如果用户同时触发两个事件,您的 ajax 事件将被触发两次,一次是 callback_id = 0,一次是 callback_id = 1

然后您要做的最后一件事是如果 callback_id 是最新的,则 if 语句仅更新您的 TypeaheadSuggestions 通过比较从您的服务器响应发回的 ID。

您必须将新的输入文本与您发送的文本进行比较,如果是用户想要查找的内容 - 您将显示它,否则不对响应执行任何操作。

例如:

var searchText = $("input").val()
$.ajax({
        ....
        data: {searchText : searchText}
        success: funtion(){
        if($("input").val()==searchText){
           //SHOW RESULTS
        }
     }
})

如果您想保持服务器端代码不变,还有另一种方法。您实际上可以将 return 函数包装在 class 中并为每个请求创建实例,然后将最新实例存储在全局范围变量中并检查调用方法的所有者是否与最新实例匹配:

var lastRequest;
var searchText = $("#searchText");

function requestClass()
{
    var that = this;

    this.showTypeaheadSuggestions = function(data) {
        //Update ui-element
        if (lastRequest == that)
            console.log('suggestions retrieved: ' + data);
        else
            console.log('this response (' + data + ') is ignored');
    };

    this.displayFailure = function() {
        //Update ui-element
        console.log('failure');
    };
}

var searchTextKeyup = function() {
    var request = new requestClass();
    lastRequest = request;

    var txt = searchText.val();
    $.ajax({
        url : typeaheadUrl(txt),
        type : 'GET',
        dataType : 'json',
    }).done(request.showTypeaheadSuggestions).fail(request.displayFailure);
}

searchText.on('keyup', searchTextKeyup);

我已经用你在问题中提出的小测试对此进行了测试(当搜索字符串与 'a' 字符匹配时添加 2 秒延迟),结果如下:

suggestions retrieved: ab
this response (a) is ignored

Promises 接口 returns 立即给您 "Promise object" 以便您可以使用不同的回调语法。

而不是:

asyncCall(callback);

您可以使用:

asyncCall()
  .then(callback);

你可以链接这些:

authorizeAccount()
  .then(getNames)
  .then(processNames);

回调将按照您注册它们的顺序执行 -- processNames 将等待 getNames 先解决。

处理这个 "correctly" 的 "established" 方法可能是添加一些客户端去抖动,这样只有整个请求('query' 而不是 'q','qu' 'que'...) 在您输入单词时处理: http://underscorejs.org/#debounce 如果返回时间过长,则响应超时。