如何使所有AJAX调用按顺序执行?

30

我使用jQuery。我不希望我的应用程序进行并行的AJAX调用,每个调用必须在开始之前等待上一个调用完成。如何实现?有任何辅助工具吗?

更新 如果有XMLHttpRequest或jQuery.post的同步版本,我想知道。但是,顺序执行不等于同步执行,我需要一种异步且顺序执行的解决方案。


4
你知道 AJAX 中的 "A" 代表什么吗? :-) - Ed S.
2
这绝对不是一个答案,但当这个问题成为困惑的问题而不是如何使它并发时,这是一个设计良好的框架的有趣迹象。 :) - Sam Harwell
8
@Ed Swangren,是的,但 AJAX 不必是异步的、JavaScript 或 XML。 :-) - Nosredna
@280Z28,谢天谢地。我还记得我有Amiga时,DOS和Mac用户总是告诉我人们不需要多任务处理。 - Nosredna
@Ed 我知道A代表异步。但是每个异步方法都可以有一个同步版本。我不知道对于AJAX,这个同步版本是否已经在某个地方实现了。 - Jader Dias
1
@Nosredna 但是AJAX怎么可能不是Javascript呢? - Jader Dias
12个回答

20

有一种比使用同步ajax调用更好的方法。Jquery ajax返回一个deferred,因此您可以使用管道链接来确保每个ajax调用在下一个运行之前完成。这是一个可工作的示例,并且有一个更详细的示例,您可以在jsfiddle上进行演示。

// How to force async functions to execute sequentially 
// by using deferred pipe chaining.

// The master deferred.
var dfd = $.Deferred(),  // Master deferred
    dfdNext = dfd; // Next deferred in the chain
    x = 0, // Loop index
    values = [], 

    // Simulates $.ajax, but with predictable behaviour.
    // You only need to understand that higher 'value' param 
    // will finish earlier.
    simulateAjax = function (value) {
        var dfdAjax = $.Deferred();

        setTimeout(
            function () {
                dfdAjax.resolve(value);
            },
            1000 - (value * 100)
        );

        return dfdAjax.promise();
    },

    // This would be a user function that makes an ajax request.
    // In normal code you'd be using $.ajax instead of simulateAjax.
    requestAjax = function (value) {
        return simulateAjax(value);
    };

// Start the pipe chain.  You should be able to do 
// this anywhere in the program, even
// at the end,and it should still give the same results.
dfd.resolve();

// Deferred pipe chaining.
// What you want to note here is that an new 
// ajax call will not start until the previous
// ajax call is completely finished.
for (x = 1; x <= 4; x++) {

    values.push(x);

    dfdNext = dfdNext.pipe(function () {
        var value = values.shift();
        return requestAjax(value).
            done(function(response) {
                // Process the response here.

            });

    });

}

有些人评论说他们不知道这段代码是干什么的。为了理解它,你首先需要了解JavaScript Promise。我非常确定Promise很快将成为JavaScript语言的原生功能,所以这应该会给你一个很好的学习动力。


1
嗨,有点晚了,但无论如何:非常感谢你的帮助,确实解决了我的问题。但是我真的不明白它是如何工作的。我试图去理解和阅读文档,但这对我来说仍是个谜。 - dan-lee
1
@DanLee 看起来你不是唯一的一个。 - MD. Sahib Bin Mahboob
很难理解JavaScript的Promise以及jQuery实现这个概念的方式,而不去学习它;-) 有很多文档可以参考。 - Todd Chaffee
我已经编辑了答案,以使其更清晰,让您了解代码所需的背景。 - Todd Chaffee
使用这种模式,您实际上是禁用了ajax请求的异步性质,而这可以通过将async: false设置为请求参数来实现。这样做会引发与您建议的行为完全相同的行为。 - kamelkev
2
@kamelkev,那不是完全正确的。设置async: false会阻止代码中等待请求返回时触发的其他事件。这是极不鼓励的,因为即使像点击这样的浏览器事件也不会被触发。我提供的代码允许异步请求仍然以严格的顺序运行。 - Todd Chaffee

6
我可以想到两种选择。其一是通过回调函数链接它们,而另一种方法则是使调用同步而非异步。
你需要让它们顺序执行吗?这样会减慢速度。
要将调用设置为同步,您需要在Ajax调用中将async选项设置为false。请参见文档(单击“选项”选项卡以查看它们)。

如何使它们同步? - Jader Dias
或者第三种选择:一个管理请求队列并按顺序处理它们的类。 - Jader Dias
我编辑了我的回答,指向Ajax选项。将async选项设置为false。 - Nosredna
是的。你可以让你的Ajax回调到达一个处理程序,该处理程序启动下一个请求。我想这可能比通过回调手动链接更容易处理。 - Nosredna
从jQuery 1.8开始,将AJAX async设置为false已被弃用。 - Paul Flahr

5
(async () => { 
  for(f of ['1.json','2.json','3.json']){
    var json = await $.getJSON(f);
    console.log(json)
 };
})()
  1. 用jQuery ajax调用请求3个json文件
  2. 按顺序(而不是并行)使用await进行处理
  3. 截至2018年1月30日,可以在Chrome / Firefox / Edge中使用

更多信息请参见MDN


2

现代的 jQuery 异步操作排序方式是使用它们已经返回的 promises 和 promises 支持的流程控制,这在之前年份的任何其他答案中都没有展示。

例如,假设你想要使用 $.getScript() 加载多个脚本,但这些脚本必须按顺序加载,以便第二个脚本在第一个脚本完成之前不会被加载/运行,而且你想知道它们何时全部完成。你可以直接使用 $.getScript() 已经返回的 promise。为了简单起见,你可以像这样在一个 for 循环中使用 await 这个 promise:

async function loadScripts(scriptsToLoad) {
    for (const src of scriptsToLoad) {
        await $.getScript(src);
    }
}

loadScripts([url1, url2, url3]).then(() => {
    console.log("all done loading scripts");
}).catch(err => {
    console.log(err);
});

由于所有jQuery异步操作现在都返回承诺(并且多年来一直如此),因此您可以将此概念扩展到任何与jQuery的Ajax相关的操作。


另外,请注意,其他答案中尝试在新承诺或jQuery延迟中包装jQuery操作的所有尝试已过时,并被认为是承诺反模式,因为当操作本身已经返回一个承诺时,您可以直接使用该承诺,而无需尝试将其包装在自己的新承诺中。


一个很好的干净的示例,展示了await的使用,这就是人们应该关注的! - tobybot

1

您可以使用Promise使Ajax调用按顺序进行。使用Array.push和pop Promise方法,顺序的Ajax调用将变得更加容易。

var promises = [Promise.resolve()];

function methodThatReturnsAPromise(id) {
  return new Promise((resolve, reject) => {
    $.ajax({
      url: 'https://jsonplaceholder.typicode.com/todos/'+id,
      dataType:'json',
      success: function(data)
      {
        console.log("Ajax Request Id"+id);
        console.log(data);
        resolve();
      }
    });
  });
} 

function pushPromise(id)
{
  promises.push(promises.pop().then(function(){
    return methodThatReturnsAPromise(id)}));
}


pushPromise(1);
pushPromise(3);
pushPromise(2);

有一个jQuery插件可以进行顺序的Ajax请求 https://foliotek.github.io/AjaxQ/ - Muthu Kumar

1

你可以像Nosredna所说的那样链接回调函数来完成这个任务,这是最好的方法。我不建议使用同步XMLHttpRequest,因为它们会锁定整个应用程序。

据我所知,没有太多辅助工具可以做到这一点,但你可以实现类似于回调FIFO的东西。


1

你可以尝试使用叙述性 JavaScript http://www.neilmix.com/narrativejs/doc/

虽然我自己从未使用过它。如果我想要这样做,我会设置某种抽象来链接异步操作。正如其他人所说,ajax 对象的同步版本会阻塞事件的处理,而它正在等待响应。这会导致浏览器看起来像是冻结了,直到它收到响应。


谢谢您的补充,但我不确定它是否解决了我的问题。如果只有一个生成线程,它会使异步调用变成顺序调用,但在我的情况下,许多事件会产生请求。 - Jader Dias
就像我之前所说的,我没有使用过NJS,但它的描述称它添加了一个yield操作符,可以简单地停止函数的执行,直到事件被触发。在我看来,这并不会阻止并发。如果是这样的话,那么它对任何人来说都不是一个非常有用的库。 - Breton
如果没有其他办法,你可以研究编译后的代码,也许你能找到一个纯JS的解决方案来解决你的问题。 - Breton

1

async选项设置为false,例如:

$.ajax({ async: false /*, your_other_ajax_options_here */ });

参考资料:Ajax/jQuery.ajax


1
async选项将很快从jQuery中删除。 - Mr. Lance E Sloan
这是对OP原始请求的最佳答案。这个建议保证了IOE(按顺序执行)并且复杂度最小。与@LS所暗示的相反,从jquery中删除异步标志从未成为计划。 - kamelkev
2
请注意:"自jQuery 1.8起,使用async:false与jqXHR($ .Deferred)已被弃用;您必须使用success / error / complete回调选项,而不是jqXHR对象的相应方法,例如jqXHR.done()。" http://docs.jquery.com/Ajax/jQuery.ajax#options - Mr. Lance E Sloan
@LS 已弃用并非意味着删除。您可以在原始票证上阅读更多相关信息:https://bugs.jquery.com/ticket/11013 - kamelkev

0
看这个:http://docs.jquery.com/Ajax/jQuery.ajax(点击“选项”标签)。
但是请记住,同步调用会冻结页面,直到收到响应,因此不能在生产站点中使用,因为用户如果不得不等待30秒并且浏览器被冻结,他们会很生气。
编辑:好的,根据您的更新,现在更清楚您想要实现什么了;)
所以,您的代码可能如下所示:
    $.getJSON("http://example.com/jsoncall", function(data) {
        process(data);
        $.getJSON("http://example.com/jsoncall2", function (data) {
            processAgain(data);
            $.getJSON("http://example.com/anotherjsoncall", function(data) {
                processAgainAndAgain(data);
            });
        });
    });

这样,第二个调用只有在第一个调用的响应已经被接收和处理后才会发出,而第三个调用只有在第二个调用的响应已经被接收和处理后才会发出。这段代码是针对getJSON编写的,但它也可以适用于$.ajax。


我了解你的解决方案,但是多个请求不是由同一个事件生成的,所以我不能像你展示的那样将它们链接在一起。我的解决方案更像是C#中的锁(globalAjaxObject)。 - Jader Dias

-2
同步调用并不一定更慢,如果您的应用程序中有AJAX调用打开、发布到、然后关闭套接字,那么对套接字的多次调用是没有意义的,因为某些套接字只能处理单个连接,在这种情况下,排队数据以便在前一个AJAX调用完成时才发送意味着更高的数据吞吐量。

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