等待多个异步调用完成后再继续进行

55

所以,我有一个页面,通过jquery.get进行多个请求,以填充下拉菜单的值。

$(function() {
    LoadCategories($('#Category'));
    LoadPositions($('#Position'));
    LoadDepartments($('#Department'));

    LoadContact();
};

然后它调用LoadContact(); 这会再次调用另一个函数,当它返回时,它会填充表单上的所有字段。问题在于,经常情况下,下拉列表没有全部填充,因此无法将其设置为正确的值。

我需要做的是,让LoadContact仅在其他方法完成并且回调执行完毕后执行。

但是,我不想在下拉列表填充回调结尾处放置一堆标志,然后检查它们,并且在调用LoadContact()之前必须有一个递归的setTimeout调用来检查。

在jQuery中是否有一些内容可以让我说:“当所有这些都完成时,请执行此操作。”?

更多信息 我正在考虑类似以下方式的内容

$().executeAfter(
    function () {   // When these are done
        LoadCategories($('#Category'));
        LoadPositions($('#Position'));
        LoadDepartments($('#Department'));
    },
    LoadContact // Do this
);

在执行方法期间,它需要跟踪发生的ajax调用,并在它们全部完成时调用LoadContact;

如果我知道如何拦截在该函数中进行的ajax,我可能会编写一个jQuery扩展来实现这一点。

我的解决方案

;(function($) {
    $.fn.executeAfter = function(methods, callback) {

        var stack = [];

        var trackAjaxSend = function(event, XMLHttpRequest, ajaxOptions) {
            var url = ajaxOptions.url;

            stack.push(url);
        }

        var trackAjaxComplete = function(event, XMLHttpRequest, ajaxOptions) {
            var url = ajaxOptions.url;

            var index = jQuery.inArray(url, stack);

            if (index >= 0) {
                stack.splice(index, 1);
            }

            if (stack.length == 0) {
                callback();
                $this.unbind("ajaxComplete");
            }
        }

        var $this = $(this);

        $this.ajaxSend(trackAjaxSend)
        $this.ajaxComplete(trackAjaxComplete)

        methods();
        $this.unbind("ajaxSend");
    };
})(jQuery);

在调用方法时,这将绑定到ajaxSend事件,并保留所调用的URL(需要更好的唯一ID)列表。 然后从ajaxSend解绑,以便仅跟踪我们关心的请求。 它还绑定到ajaxComplete并在返回时从堆栈中删除项目。 当堆栈达到零时,它执行我们的回调函数,并解绑ajaxComplete事件。


4
思维闪现...当我4个月前问过同样的问题时,为什么现在我的问题被认为是重复的了?请帮我解决翻译。 - CaffGeek
4个回答

44

你可以这样使用 .ajaxStop()

$(function() {
  $(document).ajaxStop(function() {
    $(this).unbind("ajaxStop"); //prevent running again when other calls finish
    LoadContact();
  });
  LoadCategories($('#Category'));
  LoadPositions($('#Position'));
  LoadDepartments($('#Department'));
});

这将在所有当前请求完成后运行,然后解除绑定以便在页面中执行未来的ajax调用时不会运行。同时,请确保在ajax调用之前放置它,以便它足够早地绑定,这对于.ajaxStart()更为重要,但最好在两者上都这样做。


这很好,在我所拥有的场景中似乎可以工作,但是让我们添加一个警告,有第四个异步调用需要很长时间,而我不想等待它完成,因为LoadContact不需要它。有没有办法只说这三件事需要完成? - CaffGeek
@ Chad - 不行,如果你想挑选...就得自己加一个计数器,全局处理程序无法知道你关心哪些。 - Nick Craver
我已经添加了一个看起来能够满足我的需求的解决方案。你的答案确实指引了我正确的方向! - CaffGeek
我给了你答案,因为它在加上警告之前是正确的。 - CaffGeek
1
+1 介绍我使用 ajaxStop。现在我已经成功实现了递归异步多重加载。干杯! - iCollect.it Ltd

18

根据Tom Lianza的回答,现在使用$.when()比使用.ajaxStop()要更好。

唯一的注意点是你需要确保异步方法返回Deferred对象。幸运的是,jQuery的ajax调用已经默认实现了这个功能。因此,要实现问题中的场景,需要等待的方法大致如下:

function LoadCategories(argument){
    var deferred = $.ajax({
       // ajax setup
    }).then(function(response){ 
       // optional callback to handle this response 
    });
    return deferred;
}

然后在所有三个ajax调用返回并可选地执行它们自己的回调之后调用LoadContact():

// setting variables to emphasize that the functions must return deferred objects
var deferred1 = LoadCategories($('#Category'));
var deferred2 = LoadPositions($('#Position'));
var deferred3 = LoadDepartments($('#Department'));

$.when(deferred1, deferred2, deferred3).then(LoadContact);

7

同意,那是我现在使用的。 - CaffGeek
1
延迟对象的哪个部分解决了这个问题?有很多选项,很难确定如何解决。 - Jeff Davis

0

但是,我不想在下拉列表的回调函数末尾放置一堆标志,然后再进行检查,并且必须在调用LoadContact()之前进行递归setTimeout调用检查。
无需使用setTimeout。您只需在每个回调中检查所有三个列表是否已填充(或更好地设置一个计数器,在每个回调中增加它并等待它等于3),然后从回调中调用LoadContact。对我来说似乎很容易。

ajaxStop方法也可能有效,只是我不太熟悉它。


这就是我想要避免的。回调函数不应该知道,也不关心它们之后运行什么,或者依赖于它们,或者其他回调函数是否正在执行或需要执行。这只会引入非常复杂的逻辑。 - CaffGeek
我看到一个简单的解决方案:将辅助函数作为参数传递给LoadCategories等函数('executeMeAfterCallback'),并编写您的逻辑。这很像ajaxStop,但它可以让您更好地控制哪些请求需要参与。 - Nikita Rybak
顺便问一下,你为什么要有三种不同的加载列表方法而不是一个(带有必要的参数,比如URL)?只是好奇。 - Nikita Rybak
他们都调用一个共同的函数,使用正确的URL和目标元素。包装这个共同方法的三个函数的原因是为了保持代码的清晰,如果我以后需要重新加载部门呢?或者在另一个控件中加载它们,我希望如何完成的实现被隐藏起来,因此只需要调用LoadDepartments(targetElement)。 - CaffGeek
是的,那是个好观点。不过,executeMeAfterCallback 应该可以做到这一点。稍后你总是可以调用 LoadDepartments 而不带第二个参数。 - Nikita Rybak

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