等待所有jQuery Ajax请求完成?

760

我该如何让一个函数在另一个函数中的所有jQuery Ajax请求都完成后再执行?

简而言之,我需要在执行下一个请求之前等待所有Ajax请求完成。但是如何实现呢?


你是如何调用原始的ajax请求的? - NakedBrunch
2
“完成”是什么意思?我理解为“所有请求已经完成,无论成功或失败”(已解决或已拒绝)。但您可能指的是“所有请求都已成功完成”(已解决)。请参见http://api.jquery.com/category/deferred-object/中的所有变体。 - Adriano
22个回答

1025

现在 jQuery 定义了一个 when 函数 用于此目的。

它接受任意数量的 Deferred 对象作为参数,并在所有对象都解析后执行函数。

这意味着,如果你想要发起(例如)四个 Ajax 请求,然后在它们完成时执行某些操作,可以像这样实现:

$.when(ajax1(), ajax2(), ajax3(), ajax4()).done(function(a1, a2, a3, a4){
    // the code here will be executed when all four ajax requests resolve.
    // a1, a2, a3 and a4 are lists of length 3 containing the response text,
    // status, and jqXHR object for each of the four ajax calls respectively.
});

function ajax1() {
    // NOTE:  This function must return the value 
    //        from calling the $.ajax() method.
    return $.ajax({
        url: "someUrl",
        dataType: "json",
        data:  yourJsonData,            
        ...
    });
}
在我看来,这样做可以使语法更加清晰简洁,并避免牵扯到任何全局变量,如ajaxStart和ajaxStop,这可能会在页面开发过程中产生不良影响。
如果您事先不知道需要等待多少个ajax参数(即您想使用可变数量的参数),仍然可以完成,但会更加复杂。请参见将一组Deferred对象传递给$.when()(也许还有jQuery .when用于处理可变参数的故障排除)。
如果您需要更深入地控制ajax脚本等的失败模式,则可以保存.when()返回的对象 - 它是一个包含所有原始ajax查询的jQuery Promise对象。您可以调用.then().fail()对其进行详细的成功/失败处理程序。

54
这个答案应该被标记为正确的,因为它简单、高效且工作得很好。另外,应该注意到$.when返回一个Promise对象,该对象具有更多有用的方法,不仅仅是.done。例如,使用.then(onSuccess, onFailure)方法,你可以在两个请求都成功或至少其中一个失败时做出反应。 - skalee
3
能否将ajax1..4请求打包成一个数组并传递? - andig
41
小心fail情况。与done不同,fail在第一个失败时立即触发,并忽略剩余的deferreds。 - Ryan Mohr
2
给人们展示when方法和Promise是很好的,但我认为这不是最好的答案。如果任何一个ajax函数在后续创建了另一个ajax请求,并且没有正确地将该新promise集成到链中...那么这些请求将逃脱这种技术。例如,我不能在不修改我正在使用的Shopify库的情况下使用这种技术来进行ajax添加至购物车的行为,因为它没有以“promise”方式编写,并且从不返回它创建的xhr对象。这有意义吗?尽管如此,仍然是一个很好的答案! - Ziggy
2
当请求失败时不起作用。踩一下。当答案完整时会投赞成票。这意味着,我只想在所有请求完成(包括失败的请求)后继续进行。 - Legends
显示剩余13条评论

333
如果您想知道文���中所有的ajax请求何时完成,无论有多少个,请使用$.ajaxStop事件,方法如下:

$(document).ajaxStop(function () {
  // 0 === $.active
  $(this).unbind('ajaxStop'); // to stop this event repeating further
});

在这种情况下,您既不需要猜测应用程序中有多少请求可能会在未来完成,也不需要深入函数的复杂逻辑或查找哪些函数正在进行HTTP(S)请求。 $.ajaxStop 可以绑定到任何您认为可能会被请求修改的HTML节点。

更新:
如果您想坚持使用ES语法,那么您可以使用Promise.all来处理已知的ajax方法:

Promise.all([ajax1(), ajax2()]).then(() => {
  // all requests finished successfully
}).catch(() => {
  // all requests finished, but one or more failed
})

有趣的是,它可以与Promises以及$.ajax请求一起使用。

这里是jsFiddle演示。


更新 2:
使用 async/await 句法的最新版本:

try {
  const results = await Promise.all([ajax1(), ajax2()])
  // do other actions
} catch(ex) { }

19
如果你需要处理带有匿名回调/闭包的第三方脚本,那么这个答案比其他答案要好得多。+1 - kaiser
5
@kaiser 的观点很有道理,但这并不是问题所问的。如果你不想等待所有 AJAX 调用返回,那么这并不是很好。问题是明确要求等待自己发起的 AJAX 调用(在另一个函数中调用,正如 OP 所写)。可能会有其他代码发起了另一个 AJAX 调用,而你不想等待它。 - Ruan Mendes
7
与when()解决方案相比,它的优点是即使不知道Ajax调用的数量也可以工作。 - Alexis Dufrenoy
6
与 when() 解决方案相比,它有一个很大的劣势,就是无法与其他组件良好配合,因为它共享文档范围的全局状态。如果某些长轮询持续进行,它甚至可能永远不会触发。 - Bergi
4
你的说法有误 @AdrienBe,ajaxStop会处理所有的ajax请求,无论它们成功与否。为了证明我的话,请看这个链接:http://jsfiddle.net/36votxba/2/ - Arsen Khachaturyan
显示剩余7条评论

33

我发现了一个来自 gnarf好回答,恰好满足了我的需求 :)

jQuery ajaxQueue

//This handles the queues    
(function($) {

  var ajaxQueue = $({});

  $.ajaxQueue = function(ajaxOpts) {

    var oldComplete = ajaxOpts.complete;

    ajaxQueue.queue(function(next) {

      ajaxOpts.complete = function() {
        if (oldComplete) oldComplete.apply(this, arguments);

        next();
      };

      $.ajax(ajaxOpts);
    });
  };

})(jQuery);

然后你可以像这样将ajax请求加入队列:

$.ajaxQueue({
        url: 'page.php',
        data: {id: 1},
        type: 'POST',
        success: function(data) {
            $('#status').html(data);
        }
    });

38
看起来您忘记给这个答案适当的归属,我已经添加了。 - Tim Post

22

注意:上面的答案使用了在回答这个问题时还不存在的功能。我建议使用jQuery.when()而不是这些方法,但出于历史目的,我将保留这个答案。

-

你可以通过一个简单的计数信号量来实现,尽管你如何实现会取决于你的代码。一个简单的例子可能是这样的...

var semaphore  = 0,     // counting semaphore for ajax requests
    all_queued = false; // bool indicator to account for instances where the first request might finish before the second even starts

semaphore++;
$.get('ajax/test1.html', function(data) {
    semaphore--;
    if (all_queued && semaphore === 0) {
        // process your custom stuff here
    }
});

semaphore++;
$.get('ajax/test2.html', function(data) {
    semaphore--;
    if (all_queued && semaphore === 0) {
        // process your custom stuff here
    }
});

semaphore++;
$.get('ajax/test3.html', function(data) {
    semaphore--;
    if (all_queued && semaphore === 0) {
        // process your custom stuff here
    }
});

semaphore++;
$.get('ajax/test4.html', function(data) {
    semaphore--;
    if (all_queued && semaphore === 0) {
        // process your custom stuff here
    }
});

// now that all ajax requests are queued up, switch the bool to indicate it
all_queued = true;

如果你想让它像 {async: false} 一样运作,但又不想锁定浏览器,你可以通过使用 jQuery 队列来实现相同的效果。

var $queue = $("<div/>");
$queue.queue(function(){
    $.get('ajax/test1.html', function(data) {
        $queue.dequeue();
    });
}).queue(function(){
    $.get('ajax/test2.html', function(data) {
        $queue.dequeue();
    });
}).queue(function(){
    $.get('ajax/test3.html', function(data) {
        $queue.dequeue();
    });
}).queue(function(){
    $.get('ajax/test4.html', function(data) {
        $queue.dequeue();
    });
});

11
这似乎会把一个微不足道的问题复杂化。 - Chris
2
这并不是太复杂了。计数信号量是计算机科学中常见的一种机制。但如果您更喜欢,使用jQuery队列的示例也可以工作,而无需自己实现信号量。 - BBonifield
1
我并没有看到信号量计数器上的问题,但我发现有四个函数来处理回调结果是有问题的。你应该先定义一个函数,然后在每个 .get() 中引用它。这样至少你就不会重复代码了。而且,每次声明一个 function(){} 都会分配内存!如果你可以调用静态定义的函数,那么这种做法非常糟糕。 - Alexis Wilke
1
@AlexisWilke 这是一个4.5年前的答案,它旨在说明信号量和队列的工作原理。你对此想得有点太多了,我认为用大写字母来强调并不必要。 - BBonifield
2
好吧...我不是给你打-1的那个人...我明白答案会随着时间推移变旧。可是,人们仍然会找到它们,据我所知,向那些今天可能仍然会使用它们的人提供信息并不是被禁止的。 - Alexis Wilke
显示剩余4条评论

21

使用ajaxStop事件。

例如,假设您在获取100个ajax请求时有一个正在加载...消息,并且您想要在加载完成后隐藏该消息。

来自jQuery 文档

$("#loading").ajaxStop(function() {
  $(this).hide();
});

请注意,它会等待页面上所有的ajax请求完成。


5
假设您知道页面上不会有其他 AJAX 请求,这并不是一个很好的假设。 - Ruan Mendes
从jQuery 1.8开始,.ajaxStop()方法只应该附加到文档。 - Geomorillo
1
请纠正我,但这不会将您的项目变成“老派Web表单”网站吗?我的意思是,如果整个页面必须等待请求才能继续,那么首先进行ajax请求有什么意义呢? - BillRuhl
在我们的情况下,我正在通过循环遍历 jQuery 集合来构建新的内容,并且需要在完成整个集合之后了解它的情况,然后再进行一些布局调整。这似乎并不是特别不寻常的情况。如果有一堆其他的 ajax 操作可能正在进行中,那就不好了,但在这里不会出现这种情况。 - eon

10

一个小的解决办法可以是这样:

// Define how many Ajax calls must be done
var ajaxCalls = 3;
var counter = 0;
var ajaxCallComplete = function() {
    counter++;
    if( counter >= ajaxCalls ) {
            // When all ajax calls has been done
        // Do something like hide waiting images, or any else function call
        $('*').css('cursor', 'auto');
    }
};

var loadPersons = function() {
        // Show waiting image, or something else
    $('*').css('cursor', 'wait');

    var url = global.ctx + '/loadPersons';
    $.getJSON(url, function(data) {
            // Fun things
    })
    .complete(function() { **ajaxCallComplete();** });
};

var loadCountries = function() {
    // Do things
    var url = global.ctx + '/loadCountries';
    $.getJSON(url, function(data) {
            // Travels
    })
    .complete(function() { **ajaxCallComplete();** });
};

var loadCities = function() {
    // Do things
    var url = global.ctx + '/loadCities';
    $.getJSON(url, function(data) {
            // Travels
    })
    .complete(function() { **ajaxCallComplete();** });
};

$(document).ready(function(){
    loadPersons();
    loadCountries();
    loadCities();
});

希望这能有所帮助...


虽然其他答案在技术上更好,因为它更容易理解,但我真的很喜欢这个。不错! - Jay

8

jQuery允许您指定您想要的ajax请求是同步还是异步的。您可以简单地使ajax请求同步,然后在它们返回之前,其余代码将不会执行。

例如:

jQuery.ajax({ 
    async: false,
    //code
});

45
需要注意的一件事是,使用 { async: false } 可能会暂时锁定浏览器。http://api.jquery.com/jQuery.ajax/ - BBonifield
32
这与标准的jQuery / JavaScript做法相反。 AJAX始终应该是异步的。你应该使用jQuery.when()代替。 - SystemParadox
45
这个主意真是太糟糕了!绝对不能这么做!阻塞 = 完全不响应用户的任何操作,甚至包括滚动或其他操作!(此外,async: false 在 jQuery 1.8 版本将被弃用。) - skalee
6
特别是如果由于一些无法预测的原因(根据墨菲定律,这种情况肯定会发生!)请求失败或花费很长时间,由于以上所述的浏览器锁定,这通常对生产代码不是一个好主意。 - Alex
28
这是一个非常糟糕的想法。不要使用这个答案。 - Tauren
显示剩余28条评论

8
JavaScript 是基于事件的,因此您不应该“等待”,而是设置钩子/回调函数。
您可能只需使用 jquery.ajax 的成功/完成方法。
或者您可以使用 .ajaxComplete
$('.log').ajaxComplete(function(e, xhr, settings) {
  if (settings.url == 'ajax/test.html') {
    $(this).text('Triggered ajaxComplete handler.');
    //and you can do whatever other processing here, including calling another function...
  }
});

虽然你应该发布一个伪代码来更精确地说明你的Ajax请求是如何调用的...

4
正如其他答案提到的,您可以使用 ajaxStop() 来等待所有 ajax 请求完成。
$(document).ajaxStop(function() {
     // This function will be triggered every time any ajax request is requested and completed
});

如果您想针对特定的ajax()请求执行此操作,则最好使用在该特定ajax请求内部的complete()方法:
$.ajax({
    type: "POST",
    url: "someUrl",
    success: function(data) {
        // This function will be triggered when ajax returns a 200 status code (success)
    },
    complete: function() {
        // This function will be triggered always, when ajax request is completed, even it fails/returns other status code
    },
    error: function() {
        // This will be triggered when ajax request fail.
    }
});


但是,如果你只需要等待某些特定的 ajax 请求完成怎么办?使用优秀的 JavaScript promises 来等待你想要等待的这些 ajax 请求被完成。我制作了一个简短、易懂的示例来展示 promises 如何与 ajax 协同工作。
请看下面的示例。我使用 setTimeout 来说明这个示例。

// Note:
// resolve() is used to mark the promise as resolved
// reject() is used to mark the promise as rejected

$(document).ready(function() {
    $("button").on("click", function() {

        var ajax1 = new Promise((resolve, reject) => {
            $.ajax({
                type: "GET",
                url: "https://miro.medium.com/max/1200/0*UEtwA2ask7vQYW06.png",
                xhrFields: { responseType: 'blob'},
                success: function(data) {
                    setTimeout(function() {
                        $('#image1').attr("src", window.URL.createObjectURL(data));
                        resolve(" Promise ajax1 resolved");
                    }, 1000);
                },
                error: function() {
                    reject(" Promise ajax1 rejected");
                },
            });
        });

        var ajax2 = new Promise((resolve, reject) => {
            $.ajax({
                type: "GET",
                url: "https://cdn1.iconfinder.com/data/icons/social-media-vol-1-1/24/_github-512.png",
                xhrFields: { responseType: 'blob' },
                success: function(data) {
                    setTimeout(function() {
                         $('#image2').attr("src", window.URL.createObjectURL(data));
                         resolve(" Promise ajax2 resolved");
                    }, 1500);
                },
                error: function() {
                    reject(" Promise ajax2 rejected");
                },
            });
        });

        var ajax3 = new Promise((resolve, reject) => {
            $.ajax({
                type: "GET",
                url: "https://miro.medium.com/max/632/1*LUfpOf7teWvPdIPTBmYciA.png",
                xhrFields: { responseType: 'blob' },
                success: function(data) {
                    setTimeout(function() {
                         $('#image3').attr("src", window.URL.createObjectURL(data));
                         resolve(" Promise ajax3 resolved");
                    }, 2000);
                },
                error: function() {
                    reject(" Promise ajax3 rejected");
                },
            });
        });
        
        Promise.all([ajax1, ajax2, ajax3]).then(values => {
            console.log("We waited until ajax ended: " + values);
            console.log("My few ajax ended, lets do some things!!")
        }, reason => {
            console.log("Promises failed: " + reason);
        });
        
        // Or if you want wait for them individually do it like this
        // ajax1.then(values => {
        //    console.log("Promise 1 resolved: " + values)
        // }, reason => {
        //     console.log("Promise 1 failed: " + reason)
        // });
    });

});
img {
  max-width: 200px;
  max-height: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button>Make AJAX request</button>
<div id="newContent">
    <img id="image1" src="">
    <img id="image2" src="">
    <img id="image3" src="">
</div>


2

在@BBonifield的回答的基础上,我编写了一个实用程序函数,以便信号量逻辑未在所有ajax调用中传播。

untilAjax是实用程序函数,当所有ajax调用完成时调用回调函数。

ajaxObjs是一个ajax设置对象数组[http://api.jquery.com/jQuery.ajax/]

fn是回调函数。

function untilAjax(ajaxObjs, fn) {
  if (!ajaxObjs || !fn) {
    return;
  }
  var ajaxCount = ajaxObjs.length,
    succ = null;

  for (var i = 0; i < ajaxObjs.length; i++) { //append logic to invoke callback function once all the ajax calls are completed, in success handler.
    succ = ajaxObjs[i]['success'];
    ajaxObjs[i]['success'] = function(data) { //modified success handler
      if (succ) {
        succ(data);
      }
      ajaxCount--;
      if (ajaxCount == 0) {
        fn(); //modify statement suitably if you want 'this' keyword to refer to another object
      }
    };
    $.ajax(ajaxObjs[i]); //make ajax call
    succ = null;
  };

示例: doSomething 函数使用 untilAjax

function doSomething() {
  // variable declarations
  untilAjax([{
    url: 'url2',
    dataType: 'json',
    success: function(data) {
      //do something with success data
    }
  }, {
    url: 'url1',
    dataType: 'json',
    success: function(data) {
      //do something with success data
    }
  }, {
    url: 'url2',
    dataType: 'json',
    success: function(response) {
      //do something with success data
    }
  }], function() {
    // logic after all the calls are completed.
  });
}

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