【问题标题】:Making multiple AJAX calls recursively is a bad idea递归地进行多个 AJAX 调用是个坏主意
【发布时间】:2014-11-23 02:17:26
【问题描述】:

我已经解决了这个问题。 Are recursive AJAX calls a bad idea? Justin Niessner 提供的答案很好(通过使用承诺)但我的问题有点不同。 我想递归调用10 ajax calls。就像第一个 ajax 调用完成时一样,我在视图中呈现该数据(由 ajax 调用提供),同时我将调用第二个 ajax 调用。所以我的代码示例将如下所示:

(function() {
    var downloadAsync = function(url, callback) {
        var httpRequest;
        if (window.XMLHttpRequest) {
            httpRequest = new XMLHttpRequest();
        } else if (window.ActiveXObject) {
            httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
        }
        httpRequest.onreadystatechange = function() {
            if (httpRequest.readyState === 4 && httpRequest.status === 200) {
                callback(httpRequest.responseText);
            }
        }
        httpRequest.open("GET", url, true);
        httpRequest.send();
    }

    function renderData(data) {
        //data render logic          
    }
    downloadAsync("mathmatics.json", function(response) {
        renderData(response);
        downloadAsync("science.json", function(response) {
            renderData(response)
            downloadAsync("english.json", function(response) {
                renderData(response);
                ....................................
            });
        });
    });

})();

我想尽快渲染每个数据。因此,在每次 ajax 调用的成功中,我都在渲染该数据。 这是我的问题:

这样做有什么更好的吗?在这种情况下我可以使用promises 吗? (渲染数据 有必要尽快)。如果是,那么如何,如果不是,那么进行此类调用的最佳方式是什么。

  1. 注意:在 ajax 成功时渲染数据是我的首要任务。
  2. 编辑 - 浏览器支持 - 最新的 chrome、firefox 和 IE-11。
  3. 编辑 - 根据 cmets 改进:
var array = ["mathematics.json", "science.json", "english.json"];
for (var i = 0; i < array.length; i++) {
  downloadAsync(array[i], renderData);
}

function renderData(data) {
  console.log(data);
    }

【问题讨论】:

  • 既然你没有发送任何数据,那么首先需要嵌套调用的原因是什么?如果只是为了让它们保持秩序,可以早点发送并使用承诺保持秩序
  • 如果你只是保持数据去哪里的顺序,你可以在一个简单的循环中调用所有的ajax函数,浏览器会将它们排队,但它们当然不会按顺序完成,你'必须自己保持这个顺序,但这很容易,即使没有承诺,只需将索引传递给 renderData 函数以了解完成的请求等。
  • @charlietfl 抱歉我的错误,编辑了问题。
  • 仍然没有解释为什么你不能简单地创建一个 url 的数组并循环数组。
  • @charlietfl 正如你所说,我包含了代码。我很想知道在这种情况下这是最好的解决方案吗?或者我可以改进它?

标签: javascript jquery ajax recursion promise


【解决方案1】:

由于您正在开发新的浏览器 - 有了 Promise,您的生活可以很多简单。

您正在检查自 IE6 以来不相关的内容,您可以大大简化您的工作。

方法如下:

function downloadAsync(url){
    return new Promise(function(fulfill, reject){
        var xhr = new XMLHttpRequest;
        xhr.open("GET", url);
        xhr.onload = function(){ resolve(xhr.responseText); });
        xhr.onerror = reject;
        xhr.send();
    });
}

不需要检查 XMLHttpRequest,不需要状态检查,一个简单的 onload - 注意我们还添加了一个 onerror,所以我们不会忽略错误。

现在,让我们一次下载所有三个:

// your pages
var pages = ["mathmatics.json", "science.json", "english.json"]
// your downloads and renders 
var promises = pages.map(downloadAsync).
                    /*map(JSON.parse).*/
                     map(renderData);
Promise.all(promises).then(function(){
    // code here runs when it's all done.
});

【讨论】:

    【解决方案2】:

    嵌套的 Ajax 调用有效。不过也有一些缺点:

    • 如果您要进行真正的错误处理,多个嵌套的 ajax 调用可能会变得非常混乱,而且您经常会在每个嵌套级别重复错误处理。
    • 在某些人看来,与其他技术相比,深度嵌套会导致代码的可读性降低
    • 如果嵌套中的任何代码都可以抛出异常,则需要大量异常处理程序才能正确捕获所有异常,并且很难干净地传播错误 - 因为这些是异步回调,您不能只捕获异常一处。

    既然问题的意图已经明确,请参阅答案末尾的编辑

    至于其他方法,使用 Promise 有一些优势。由于您的任何调用都不依赖于前一组调用,并且您只想以正确的顺序插入结果,因此您可以通过一次发送所有 ajax 请求然后序列化结果来显着加快端到端时间按顺序回来。这可以通过手动编码完成,也可以通过 Promise 轻松完成。

    你没有说你打算使用哪个 promise 库(有几个),但这里有一个使用 Bluebird 完成的(尽管代码与其他库相似或相同):

    (function() {
        // downloadAsync now returns a promise rather than uses a callback
        function downloadAsync(url) {
            return new Promise(function(resolve, reject) {
                var httpRequest = new XMLHttpRequest();
                httpRequest.onreadystatechange = function() {
                    if (httpRequest.readyState === 4) {
                        if (httpRequest.status === 200) {
                            resolve(httpRequest.responseText);
                        } else {
                            reject(httpRequest.status);
                        }
                    }
                }
                httpRequest.open("GET", url, true);
                httpRequest.send();
            }
        }
    
        function renderData(data) {
            //data render logic          
        }
        var items = ["mathmatics.json", "science.json", "english.json"];
        var promises = items.map(function(path) {
            return downloadAsync(path);
        });
    
        // Promise.all collects all the promise results and 
        // calls .then only when all the promises have completed
        Promise.all(promises).then(function(data) {
            data.forEach(renderData)
        }).catch(function(e) {
            // handle errors here
        });
    
    })();
    

    这具有性能优势,即一次请求所有数据,然后按原始请求顺序处理结果。与使用 request-response-request-response-request-response 进行序列化相比,这缩短了端到端时间。这从 request-request-request 开始,Promise.all 为我们完成工作,将结果按正确的顺序排列,以便我们按照请求的顺序处理它们。


    如果您想尽快显示任何列,您甚至不必等待全部完成,代码的第二部分可以是这样的:

    // note we are passing an index here so you know which column it is in case that isn't
    // already specified in the data
    function renderData(index, data) {
        //data render logic          
    }
    
    // are you sure you want a leading / on only one of these three?
    var items = ["mathmatics.json", "science.json", "english.json"];
    items.forEach(function(value, index) {
        downloadAsync(value).then(function(data) {
            renderData(index, data);
        });
    });
    

    在这里,Promise 对你的好处不如你可以用你的回调机制编写一个类似的结构。


    如果没有承诺,您可以使用原来的 downloadAsync() 函数并执行此操作:

    downloadAsync("mathmatics.json", renderData);
    downloadAsync("science.json", renderData);
    downloadAsync("english.json", renderData);
    

    这将并行启动所有三个,并在数据到达时立即渲染每个。


    或者,如果您有三个以上的文件名并将文件名放在任意长度的数组中,您可以这样做:

    ["mathmatics.json", "science.json", "english.json"].forEach(function(item) {
       downloadAsync(item, renderData);
    });
    

    【讨论】:

    • @pandit - 注意:根据您所针对的浏览器版本,您可能需要使用诸如 Bluebird 或 Q 之类的 Promise 库来保证对 Promise 的支持。
    • ,我有 10 列,我在列中显示每个 ajax 调用的数据,我的想法是尽快显示数据。这就是我这样做的原因。在您的方法中,我只能在所有 ajax 调用完成后才呈现整个数据。
    • @pandit - 我这样编码是因为你的原始代码就是这样工作的(一次只做一个),我假设你想要那样。如果要求或愿望不同,则不必如此。我可以在我的答案中添加一个这样的例子。
    • 没错,我担心的是,只有“在这里,Promise 并没有像你可以用你的回调机制编写相同的结构那样让你受益。”您能否为承诺实时用例提供任何好的链接。非常感谢。
    • 直到今天,我还从来没有见过可以在 if(window.XMLHttpRequest) 旁边使用 Promise 和 Array#forEach 的代码:D
    猜你喜欢
    • 2014-05-21
    • 1970-01-01
    • 1970-01-01
    • 2011-12-15
    • 2011-10-21
    • 1970-01-01
    • 1970-01-01
    • 2016-04-04
    • 1970-01-01
    相关资源
    最近更新 更多