【问题标题】:jQuery Deferred/Promises dynamic array not executing callbacks in correct orderjQuery Deferred/Promises 动态数组未按正确顺序执行回调
【发布时间】:2012-10-29 02:27:14
【问题描述】:

感谢您了解我在这里的误解。我的要求如下:

我有一个 URL 数组。我想同时为每个 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 请求完成后,才会触发任何回调。

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

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

我的尝试:

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'。

我显然从根本上误解了 Promise 的工作方式。任何帮助都将不胜感激!

干杯

编辑

好吧,现在我更困惑了。我制作了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 的承诺,没有它们就可以工作。 :)

【问题讨论】:

  • 每个请求的回调是一样的吗?
  • 差不多,它只是将返回的 HTML 解析为与请求的 URL 对应的特定元素,并将其插入到 DOM 中。
  • 在这种情况下是否可以使用 async:false 选项?
  • @logic8 不幸的是没有,因为请求可能需要相当长的时间才能完成,并且 UI 需要始终保持响应。

标签: javascript jquery asynchronous jquery-deferred promise


【解决方案1】:

异步请求不能保证按照它们发送的顺序完成。根据服务器负载和传输的数据量,有些可能比其他需要更长的时间。

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

【讨论】:

  • 对于延迟对象,应该完全有可能使每个异步请求的 回调 以所需的顺序运行,即使请求本身不会。
【解决方案2】:

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

认为这会起作用,与您的代码的主要区别在于我使用了.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();
        });
    }
})();

【讨论】:

  • 哇...首先,使用.map() 进行了很棒的重构。那么使用done() 而不是then() 有什么区别呢?据我了解,如果我在后续的 Promise 中不使用回调的返回值,实际上并没有什么区别。
  • 在我的代码中尝试过这个,它似乎仍然以随机顺序执行。话虽如此,我认为我需要创建一个简化的测试用例,因为很可能还有其他力量在起作用......
  • @ChrisFrancis 公平地说,我写这篇文章时并没有意识到它与您已经尝试过的内容有多接近。它真的不可能以随机顺序执行,因为下一个承诺的回调甚至在当前承诺完成之前都不会被注册。
  • 是的,我想我在其他地方发现了一个让我失望的错误。现在只是创建一个小提琴,很快就会在这里发布。
【解决方案3】:

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

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";
                }
            });
        }
    });
})

【讨论】:

  • 啊,好吧,所以您将每个响应缓存在一个数组中,每次收到响应时都迭代该响应数组,并在遇到 0(不完整响应)时退出......很好!这是一种非常有趣的方法!
  • 谢谢。我更新了我的答案以包括响应失败时的错误处理,并将一些代码移动到 complete 方法,这样它会在每次 .ajax 调用结束时执行。
  • 好的,我终于成功了!我调整了你的方法(i 没有在闭包中传递,所以当回调出现时总是在最后一个值上);请在问题末尾查看我的编辑。再次感谢您帮助我以不同的方式思考这个问题!
猜你喜欢
  • 1970-01-01
  • 2020-01-03
  • 2011-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-11
  • 1970-01-01
  • 2019-07-01
相关资源
最近更新 更多