jQuery Deferred / Promises动态数组没有以正确的顺序执行回调

时间:2012-11-08 19:54:37

标签: javascript jquery asynchronous jquery-deferred promise

感谢任何有关我在这里误解的内容的见解。我的要求如下:

我有一系列网址。我想同时为每个URL发出一个AJAX请求,并在第一个请求完成后立即调用第一个回调。然后,如果第二个请求完成,请调用该回调,依此类推。

选项1:

for (var i = 0; i < myUrlArray.length; i++) {
    $.ajax({
        url: myUrlArray[i]
    }).done(function(response) {
        // Do something with response
    });
}

显然这不起作用,因为无法保证答案会以正确的顺序完成。

选项2:

var promises = [];
for (var i = 0; i < myUrlArray.length; i++) {
    promises.push($.ajax({
        url: myUrlArray[i]
    }));
}
$.when.apply($, promises).then(function() {
    // Do something with each response
});

这应该可行,但缺点是它会等待所有 AJAX请求完成,然后再触发任何回调。

理想情况下,我应该能够在完成后立即调用第一个回调,然后将第二个回调链接到每当收到响应时执行(或者如果已经解决则立即执行),然后是第三个,依此类推。 / p>

数组长度是完全可变的,并且在任何给定时间都可以包含任意数量的请求,因此只是对回调链进行硬编码不是一种选择。

我的尝试:

var promises = [];
for (var i = 0; i < myUrlArray.length; i++) {
    promises.push($.ajax({
        url: myUrlArray[i] // Add each AJAX Deferred to the promises array
    }));
}
(function handleAJAX() {
    var promise;
    if (promises.length) {
        promise = promises.shift(); // Grab the first one in the stack
        promise.then(function(response) { // Set up 'done' callback
            // Do something with response

            if (promises.length) {
                handleAJAX(); // Move onto the next one
            }
        });
    }
}());

问题是回调以完全随机的顺序执行!例如,如果我将'home.html','page2.html','page3.html'添加到数组中,则响应的顺序不一定是'home.html','page2.html','page3 html的”。

我显然从根本上误解了承诺的工作方式。感谢任何帮助!

干杯

修改

好的,现在我更加困惑了。我使用this JSFiddle使用一个数组Alnitak's answer,使用JoeFletch's answer创建了另一个数组,并且它们都没有像我期望的那样工作!谁能看到这里发生了什么?

编辑2

搞定了!根据JoeFletch在下面的回答,我按如下方式调整了解决方案:

var i, responseArr = [];

for (i = 0; i < myUrlArray.length; i++) {
    responseArr.push('0'); // <-- Add 'unprocessed' flag for each pending request
    (function(ii) {
        $.ajax({
            url: myUrlArray[ii]
        }).done(function(response) {
            responseArr[ii] = response; // <-- Store response in array
        }).fail(function(xhr, status, error) {
            responseArr[ii] = 'ERROR';
        }).always(function(response) {
            for (var iii = 0; iii < responseArr.length; iii++) { // <-- Loop through entire response array from the beginning
                if (responseArr[iii] === '0') {
                    return; // As soon as we hit an 'unprocessed' request, exit loop
                }
                else if (responseArr[iii] !== 'done') {
                    $('#target').append(responseArr[iii]); // <-- Do actual callback DOM append stuff
                    responseArr[iii] = 'done'; // <-- Set 'complete' flag for this request
                }
            }
        });
    }(i)); // <-- pass current value of i into closure to encapsulate
}

TL; DR:我不理解jQuery的承诺,让它在没有它们的情况下运行。 :)

3 个答案:

答案 0 :(得分:6)

不要忘记您不需要立即注册回调。

认为这会有效,与您的代码的主要区别在于我使用了.done而不是.then并重构了几行。

var promises = myUrlArray.map(function(url) {
    return $.ajax({url: url});
});

(function serialize() {
    var def = promises.shift();
    if (def) {
        def.done(function() {
            callback.apply(null, arguments);
            serialize();
        });
    }
})();

答案 1 :(得分:3)

异步请求无法保证以与发送顺序相同的顺序完成。有些可能需要更长的时间,具体取决于服务器负载和传输的数据量。

唯一的选择是要么等到它们全部完成,一次只发送一个,要么只是处理它们可能无序。

答案 2 :(得分:3)

这是我尝试解决这个问题。我更新了我的答案,包括错误处理失败的.ajax电话。我还将一些代码移到complete调用的.ajax方法。

var urlArr = ["url1", "url2"];
var responseArr = [];
for(var i = 0; i < length; i++) {
    responseArr.push("0");//0 meaning unprocessed to the DOM
}

$.each(urlArr, function(i, url){
    $.ajax({
        url: url,
        success: function(data){
            responseArr[i] = data;
        },
        error: function (xhr, status, error) {
            responseArr[i] = "Failed Response";//enter whatever you want to place here to notify the end user
        },
        complete: function() {
           $.each(responseArr, function(i, element){
                if (responseArr[i] == "0") {
                    return;
                }
                else if (responseArr[i] != "done")
                {
                    //do something with the response
                    responseArr[i] = "done";
                }
            });
        }
    });
})