jQuery,$.ajax与URL数组

12

我有一个简单的URL数组,并且我想使用jQuery加载每个URL。我一开始使用$.get,但是我似乎无法使用$.Deferred使其工作,因此我切换到$.ajax - 我几乎让它工作了,但我得到的结果有点奇怪。我希望有人能帮助我让它更好地工作。

var results = [], files = [
   'url1', 'url2', 'url3'
];

$.when(
   $.ajax(files[0]).done(function(data) { 
      results.push(data); console.log("step 1.0"); 
   }),
   $.ajax(files[1]).done(function(data) { 
      results.push(data); console.log("step 1.1"); 
   }),
   $.ajax(files[2]).done(function(data) {
      results.push(data); console.log("step 1.2"); 
   })
).then(function(){
   console.log("step 2");
});

这应该输出..

  • 步骤 1.0
  • 步骤 1.1
  • 步骤 1.2
  • 步骤 2

然后results数组包含了所有3个ajax请求的结果。这可行吗?


1
你在这方面遇到了什么问题?请求可能会以不同的顺序完成,因此你可能会得到 步骤1.2、步骤1.0、步骤1.1、步骤2,但数组总是在执行then()之前被填充。 - Rory McCrossan
我绝对需要它们按正确顺序完成。这对代码的正确运行非常重要。 - Ciel
我得到了不同的结果。在任何步骤1之前,我都会先执行“步骤2”。 - Ciel
是的,我明白。但我不知道如何通过其他方式循环遍历数组并等待其完成。数组中项目的实际顺序并不像它们都在第二步之前完成那样重要 - 而现在,它们并没有按照这种方式工作。 - Ciel
这个库可以让你轻松地完成你需要做的事情:https://github.com/caolan/async请查看series或者eachSeries方法。 - Mike
显示剩余4条评论
2个回答

11

首先,您需要决定您是否希望您的三个ajax调用并行处理(同时运行,总运行时间较短)还是按顺序处理,其中一个ajax调用运行完成后,启动下一个ajax调用。这是一个关键的设计决策,影响您如何做到这一点。

当您使用$.when()时,您将在并行中启动所有三个ajax调用。如果仅在所有调用完成时检查结果,则仍然可以按特定顺序处理结果(因为只有在所有结果可用且按请求顺序提供时才会处理它们)。但是,以这种方式执行所有ajax调用将立即发送所有请求。如果对于此类请求而言,这是可行的,那么这通常是更好的方法,因为这将为您提供更好的端到端时间。

为此,您可以将您所拥有的内容重组成以下形式:

并行运行

var files = [
   'url1', 'url2', 'url3'
];

$.when($.ajax(files[0]),$.ajax(files[1]),$.ajax(files[2])).done(function(a1, a2, a3) {
   var results = [];
   results.push(a1[0]);
   results.push(a2[0]);
   results.push(a3[0]);
   console.log("got all results")
});
因为您等待至$.when().done()处理程序被调用时,所有ajax结果都会立即准备好,并且它们由$.when()按照请求的顺序呈现(无论哪个实际上先完成),因此您尽可能快地获得结果并以可预测的顺序呈现。
请注意,我还将results数组的定义移动到$.when()的处理程序中,因为那是您知道数据实际上有效的唯一位置(由于时间原因)。

并行运行 - 迭代任意长度数组

如果您有一个更长的数组,您可能会发现使用像.map()这样的东西迭代遍历整个数组以在循环中处理它们比单独列出它们要好:

var files = [
   'url1', 'url2', 'url3', 'url4', 'url5', 'url6', 'url7'
];

$.when.apply($, files.map(function(url) {
    return $.ajax(url);
})).done(function() {
    var results = [];
    // there will be one argument passed to this callback for each ajax call
    // each argument is of this form [data, statusText, jqXHR]
    for (var i = 0; i < arguments.length; i++) {
        results.push(arguments[i][0]);
    }
    // all data is now in the results array in order
});

按顺序执行Ajax调用

如果你需要按顺序执行Ajax调用,使得第二个调用在第一个完成后再开始(如果第二个Ajax调用需要第一个Ajax调用的结果才能确定请求或完成特定操作,则此功能可能是必需的),那么您需要使用完全不同的设计模式,而$.when()并不能胜任这种任务(它只能执行并行请求)。在这种情况下,您可能只需要使用x.then().then()来链接你的结果,然后您可以按照所要求的顺序输出日志,就像这样。

  $.ajax(files[0]).then(function(data0) {
      console.log("step 1.0");
      return $.ajax(files[1]);
  }).then(function(data1) {
      console.log("step 1.1");
      return $.ajax(files[2]);
  }).done(function(data2) {
      console.log("step 1.2");
      // all the ajax calls are done here
      console.log("step 2");
  });

控制台输出:

step 1.0
step 1.1
step 1.2
step 2

如果您的文件数组较长,可以将此结构放入循环中,以便自动运行N个顺序的 ajax 调用。虽然您可以在进行操作时将结果收集到 results 数组中,但通常情况下执行顺序的原因是前面的结果会被下一个 ajax 调用使用,因此通常只需要最终结果。如果您想在进行操作的同时收集结果,您肯定可以在每个步骤中将它们推入 results 数组。

请注意,Promise 在此处提供的优势在于,您可以按顺序执行操作,同时保持相同的嵌套层级,而不会越来越深嵌套。


顺序执行 Ajax 调用 - 迭代任意长度的数组

以下是循环中的顺序执行调用的示例:

var files = [
   'url1', 'url2', 'url3', 'url4', 'url5', 'url6', 'url7'
];

var results = [];
files.reduce(function(prev, cur, index) {
    return prev.then(function(data) {
        return $.ajax(cur).then(function(data) {
            console.log("step 1." + index);
            results.push(data);
        });
    })
}, $().promise()).done(function() {
    // last ajax call done
    // all results are in the results array
    console.log("step 2.0");
});

控制台输出:

step 1.0
step 1.1
step 1.2
step 1.3
step 1.4
step 1.5
step 1.6
step 2

Array.prototype.reduce()方法在这里非常有用,因为它在处理每个单独的数组元素时累加一个单一的值,这正是你在为每个数组元素添加.then()时需要做的。使用$().promise()创建一个空/已解决的Promise(也有其他方法可以创建这样的Promise),.reduce()迭代从此开始,这仅给我们提供了一个已经解决并且可以开始使用.then()的东西。


这非常有帮助。我开始意识到使用外部库的智慧,即使它可能会产生额外的开销。现在我只需要在一个地方执行此操作,但如果我需要执行两次或更多次,则使其正常工作所需的额外工作量将变得更加繁琐。 - Ciel
@Ciel - 我不理解你的评论。为什么需要外部库?您只需再添加几行代码即可将这些选项中的任何一个转换为可重用函数。 - jfriend00
抱歉,我分心了一段时间,今天才有机会真正尝试一下。非常感谢您的帮助,仅从这个例子中我就学到了很多。 - Ciel
嗨@jfriend00,第二个代码片段有些问题。如果数组中只有一个url,则返回参数数组将变为[Object,“success”,Object],而不是[[Object,“success”,Object]]。因此,在这种情况下,我们应该只取第一个而不是全部。顺便说一句,我是另一个Ciel。哈哈。 - Ciel
@Ciel - 是的,这是$.when()的一个烦恼,因为如果只传递一个参数时,它的行为会有所不同。 - jfriend00

3

你应该从.then中访问返回值,而不是每个.done。此外,.map可以帮助你。

var results = [], files = [
   'url1', 'url2', 'url3'
];

$.when.apply($, $.map(files, function (file) {
    return $.ajax(file);
})).then(function (dataArr) {
    /* 
     * dataArr is an array of arrays, 
     * each array contains the arguments 
     * returned to each success callback
     */
    results = $.map(dataArr, function (data) {
        return data[0]; // the first argument to the success callback is the data
    });
    console.log(results);
});

传递给.then的参数顺序将与传递给.when的顺序相同。

1
我认为你没有正确处理 $.when() 返回的数据。它不会给你一个包含数组的单一参数,而是会给你一系列的参数,每个参数都是三个值的数组。请查看此处的倒数第二个代码示例,第3行。(http://api.jquery.com/jquery.when/) - jfriend00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接