【问题标题】:Are recursive AJAX calls a bad idea?递归 AJAX 调用是个坏主意吗?
【发布时间】:2014-05-21 07:51:28
【问题描述】:

我有一个简单的函数来拉入一组模板:

function getTemplates(names, done, templates, index) {
    if (!index) index = 0;
    if (!templates) templates = {};
    if (index === names.length) return done(templates);
    $.ajax({
        url: '/templates/' + names[index] + '.min.html',
        success: function (data, status, xhr) {
            templates[names[index++]] = data;
                return getTemplates(names, done, templates, index);
        }
    });
}

对我来说,从一个到下一个直到它们都被检索到,然后回调到调用函数,这似乎是合乎逻辑的。但我很好奇这样做是否有任何不良的副作用。到目前为止我还没有看到任何问题,但我不想在没有先了解任何潜在问题的情况下将其投入生产。


更新: 在 Google 和 BenjaminGruenbaum 的帮助下,我设计了一个解决方案:

function getTemplatesAsync(names, done) {
    var calls = [];
    var templates = {};
    names.forEach(function (name, index) {
        calls.push(
            $.ajax({
                url: '/templates/' + names[index] + '.min.html',
                success: function (data, status, xhr) {
                    templates[names[index++]] = data;
                }
            })
        );
    });
    $.when.apply($, calls).done(function () {
        // using "templates" here feels fragile for some reason.  Am I wrong?
        return done(templates);
    });
}

我在这里使用templates 是因为我需要能够按名称引用每个模板,但不知何故感觉它很脆弱或不可靠。这样做看起来安全吗?

【问题讨论】:

  • 你最好在一个 ajax 请求中获取所有数据
  • 虽然它看起来是递归的(我也为此使用术语递归 Ajax),但从技术上讲,您的函数在再次调用之前就退出了,所以实际上并不是递归的......也许我们应该称它们为“链式” ?阿贾克斯 :)
  • 简单概括的答案是否定的,这不安全!原因是,Web 服务器可以并且确实通过多种方式限制同时连接的数量。因此,您在问题中绘制的场景可能会很好地测试,但在您上线时无法扩大规模。如果服务器在您的控制之下并且您理解它并且您确信它会扩展,那么一定要允许同时请求的洪流。但是,我的一般建议是让您的客户端脚本发出顺序请求。与服务器完全关闭相比,性能损失将是微不足道的!
  • @Roamer-1888,所以本杰明的回答似乎符合我的期望,但我的初始代码是最安全的方法。它按顺序处理所有请求,这对服务器的压力较小。
  • @Stephen,是的,这是我的观点,但前提是,正如我已经指出的那样,如果你可以设计一个测试策略来让你相信服务器可以承受压力,那么无论如何允许请求种子。

标签: jquery ajax recursion promise


【解决方案1】:

是的。以这种方式进行多个 AJAX 调用是一个坏主意,但可能不是出于您认为的原因。

这将导致您的所有调用按顺序执行,而不是并行进行调用并等待它们以这种方式完成。

您最好使用promises 拨打所有电话,然后等待所有电话都完成后再继续。它看起来像:

var promises = [], templates = [], i;
for(i = 0; i < names.length; i++) {
    promises.push($.get('/templates/' + names[i] + '.min.html'));
}

$.when.apply($, promises).done(function(responses) {
   for(i = 0; i < responses.length; i++) {
       templates.push(responses[i][0]);
   }
});

【讨论】:

  • 一个例外是当您需要限制您的流量时。移动浏览器不会同时处理大量请求。
  • 承诺,承诺... ;)
  • 这种方法对我不起作用。 $.when.apply(...).done() 正在执行,但 responses 的值是单个 Promise,而不是一组结果。
  • @StephenCollins 它是一个数组,数组中的每个元素(responses[0]、responses[1]、...)都是单个调用的结果。
  • 你应该考虑在这里使用.map,而不是创建一个数组并推送给它。
【解决方案2】:

虽然它看起来是递归的(我也使用术语recursive Ajax),但从技术上讲,您的函数在再次调用之前退出,所以实际上并不是递归的......也许我们应该称它们为“链接”Ajax 调用,因为它只是将异步事件链接在一起? :)

如果您不介意一次将 Ajax 请求排队,那么这样做没有问题。我以这种方式使用了几次,以确保带宽使用是可以接受的。

您需要小心边缘情况,以便它优雅地处理服务器错误。

这实际上是一种很好的技术来处理移动设备,因为它会在一次发出大量请求时阻塞。

我写了一个插件,它通过 Ajax 加载大量应用程序表单的各个部分,发现像 iPad 这样的设备只能同时处理少量的 Ajax 请求。我最终使用递归/链式 Ajax 调用来解决问题(并且还没有阻塞我们的服务器):)

【讨论】:

  • 当然它是递归的,递归并不意味着你必须在堆栈中将一个函数堆叠在它自己的顶部——这意味着逻辑以一种自我相似的方式重复:即“对 getTemplates 的调用意味着做 x, y 然后调用 getTemplates"。
  • @Benjamin Gruenbaum:编程中的递归通常被定义为被定义的函数被应用在它自己的定义中,这足以涵盖这种情况。您的x,y then calling getTemplate 示例与这种情况不匹配,因为这里它只是在匿名回调函数中注册自己以供​​以后处理,然后放弃控制。这些调用是链式的,而不是典型的递归意义上的递归,但我意识到这只是扯淡。当我开始编码时,the white book 中的递归定义是“查看递归”:)
【解决方案3】:

您更新后的代码比最初的代码要好得多,但它仍然存在一些问题,主要问题是混合了承诺和回调,而不是使用语言工具(返回值)而不使用映射。

一些改进可以是:

  • 返回承诺而不是回调参数
  • 在 push 中使用 .map 而不是 forEach。
  • 使用.then 而不是成功回调来避免同一事物的两个处理程序和可能未指定的行为(是否先执行when?success:?)

我们可以这样做:

function getTemplatesAsync(names) {
    return $.when.apply(null,names.map(function (name, index) {
        return $.get('/templates/' + names[index] + '.min.html');
    }).then(function(results){
         // map arguments to names
         return Array.prototype.reduce.call(arguments, function(obj,cur,idx){
               obj[names[idx]] = cur;
               return obj;
         },{});
    });
}

您可以这样做:

getTemplatesAsync(names).then(function(templates){
     // access templates here
});

【讨论】:

  • 公平地说,正确的解决方案可能是将模板压缩在一起并发出一个 AJAX 请求。
  • 它似乎没有按预期进行。 obj[names[idx]] = cur; 似乎按预期进行,但 then 回调中的 templates 未定义。
  • @StephenCollins missin return obj for the reducer,当你返回 undefined 时,它会将其设置为 undefined 以便下次调用,很快就会修复。
  • 已修复 :) 确保您了解它解决问题的原因。
  • .then() 之前需要第二个),对于我的目的,它必须是cur[0]。我正在调查reduce;我以前从未使用过它。此外,原则上缩小/连接模板对我来说是有意义的,但如果它们都在一个文件中,我将如何区分它们?
猜你喜欢
  • 2014-11-23
  • 1970-01-01
  • 1970-01-01
  • 2011-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多