【问题标题】:JavaScript how to determine when AJAX calls are done in a loopJavaScript如何确定AJAX调用何时在循环中完成
【发布时间】:2020-10-19 03:58:32
【问题描述】:

过去几天我一直在尝试解决这个问题,并阅读了 StackOverflow 和其他网站上的各种解决方案。

我正在构建一个站点,该站点从外部来源获取 XML 数据,然后根据结果获取更多 XML 以构建网络图。 问题是我必须等到这个 AJAX 调用循环(可能会循环到更多 AJAX 调用)完成后再进行绘制。

我不知道这是否有特别高的认知负担,但它真的让我难过。

我的代码:

function cont(outerNodes) {

    for (var i = 0; i < outerNodes.length; i++) {
        var node = outerNodes.pop();
        getXML(node["label"], node["id"]);
    } 
    // I want the code to wait until loop is done, and then draw.
    draw(nodes, edges);
}

function getXML(term, fromId) {

    var url = someURL;
    $.ajax({
        url: url,
        dataType: "xml",
        success: function(result) {
            var outerNodes = process(result, fromId, term);
            cont(outerNodes);
        }
    });

}

注意:我知道我在这里可能完全误解了 JavaScript 同步性,我很可能是。我过去曾成功使用过回调和承诺,但我似乎无法理解这一点。

如果我还没有完全清楚,请告诉我。

我确实尝试过实现一个在 process() 函数中递增的计数器,如下所示:

if (processCount < 15) {
    for (var i = 0; i < outerNodes.length; i++) {
        var node = outerNodes.pop();
        getXML(node["label"], node["id"]);
    }
} else {
    draw(nodes, edges);
}

但是,这最终导致了几次 draw() 调用,这让我的表现非常糟糕。

【问题讨论】:

  • 这个场景最好使用promise。否则你可以添加一个计数器,检查计数器数量是否达到,然后执行draw方法。
  • 我使用了计数器的方法。虽然它可以工作,但最终会出现多个绘图调用,这让我的表现变得很糟糕。我不确定如何在这里实现承诺。
  • 代码是怎么写的?你能把它粘贴到问题中吗?
  • 添加到底部。
  • 你的代码其实有一些错误,你是什么时候增加processCount的?

标签: javascript jquery ajax


【解决方案1】:

布拉德的回答将代码更改为同步,对我来说这违背了目的。如果您一直在等待所有请求完成,则可能需要一段时间,而普通浏览器可以处理多个请求。

您在原始问题中遇到的问题与范围有关。由于对cont(outerNodes) 的每次调用都会触发它自己的作用域,因此它不知道调用在做什么。所以基本上如果你调用cont(outerNodes) 两次,每个调用都会处理它自己的outernodes 列表,然后调用draw。 解决方案是在不同范围之间共享信息,这可以通过一个,但最好是两个全局变量来完成:1 用于跟踪活动进程,1 用于跟踪错误。

var inProcess = 0;
var nrErrors = 0;

function cont(outerNodes) {

    //make sure you have outerNodes before you call outerNodes.length
    if (outerNodes) {
      for (var i = 0; i < outerNodes.length; i++) {
          var node = outerNodes.pop();
          inProcess++; //add one more in process
          getXML(node["label"], node["id"]);
      } 
    }

    //only trigger when nothing is in proces. 
    if (inProcess==0) {
      // I want the code to wait until loop is done, and then draw.
      draw(nodes, edges);
   }
}

function getXML(term, fromId) {

    var url = someURL;
    $.ajax({
        url: url,
        dataType: "xml",
        success: function(result) {
            var outerNodes = process(result, fromId, term);
            inProcess--; //one is done
            cont(outerNodes);
        },
        error: function() {
            inProcess--; //one is done
            nrErrors++; //one more error
            cont(null); //run without new outerNodes, to trigger a possible draw
        }
    });

}

请注意,我跟踪nrErrors,但不对其进行任何操作。您可以使用它来停止进一步的处理,或者警告用户绘制未完成。

[重要] 请记住,这在javascript 中有效,因为它充其量只是模仿多线程。这意味着对inProcess--;cont(outerNodes); 之后的调用总是直接在彼此之后执行。

如果您将其移植到真正的多线程环境中,很可能cont(null); 的另一个作用域/版本会插在两条线之间,仍然会有多次绘制。

【讨论】:

  • 不,我的代码没有使其同步。它使用await 等待异步任务,然后再进行下一项。他们一次只想要一个,这不是问题的全部意义吗?此外,即使您想同时发出所有请求,我的答案中的循环仍然可以完成,同时仍然使用await 来处理响应。我实际首选的方法是使用 Semaphore (npmjs.com/package/await-semaphore),这样我就可以控制同时运行的事物的数量,但这似乎超出了这个问题的范围。
  • 如果您等待每一个 ajax 调用,那么它们会按照您调用它们的确切顺序运行和完成,从而使其同步。即使实际的 ajax 调用是异步的,您的 cont 函数也是同步的。目标不是一次一个。每个调用都可以添加 x 个要发出的请求,并且它们都需要完成。 10个电话添加10个新电话,不需要等待100次。理论上,他们可以一次跑 10 次,完成速度快 10 倍。
  • 如果你想一次做多个,使用信号量,就像我说的那样。如果您要请求大量小数据或来自不同服务器,这非常合适。这很少适用于更大的事情。当然,浏览器只能同时处理少数几个请求,它会透明地为您排队,但您会发现,如果您将几百个请求排队,浏览器会一并取消它们。 (如果不这样做就好了,但这是现实。)
  • 这不是攻击,我只是说您的代码改变了循环的工作方式。然后我做了这个网站的用途:我解释了为什么它不工作以及需要做什么,并举例说明了如何去做。显然,还有一百种其他方法可以做到这一点,并且需要考虑到一些事情,但我不是受雇进行性能/代码审查的。我试图帮助他理解发生了什么以及为什么他的逻辑失败了。这样他就可以自己解决下一个问题了。
【解决方案2】:

我们可以使用一些很好的新 API 和语言结构,这些 API 和语言结构得到很好的支持。 Fetch APIawaitfor...of loops

Fetch API 使用 Promises。可以等待承诺。 for...of 循环知道await 并且在await 过去之前不会继续循环。

// Loop through, one-at-a-time
for (const node of outerNodes) {
  // Make the HTTP request
  const res = await fetch(someUrl);
  
  // Do something with the response here...
}

不要忘记try/catch(也适用于await),并检查res.ok

【讨论】:

  • 谢谢!我相信我遵循,我只是不完全确定 getXML 函数在哪里工作?我可能只是误解了解决方案。
  • @Zmalski 对fetch() 的调用将替换您的AJAX 调用。然后,您需要调用res.text() 并使用DOMParser() 来解析XML。 (这个答案有更多关于在您获得 Fetch 响应后解析 XML 的详细信息:stackoverflow.com/a/41009103/362536
  • 我明白了,所以如果我正确地跟随你,获取数据,你将其转换为文本然后将其解析为 XML?然后我可以将哪个输入到我的 process() 函数中?
  • @Zmalski 是的,基本上。 fetch() 实际上返回一个 Response (developer.mozilla.org/en-US/docs/Web/API/Response),它会在 HTTP 响应标头完成后立即返回。因此,在fetch() 承诺解决时,响应主体不必在那里。这使您可以根据需要检查标头(或者在您的情况下,您应该检查res.ok)并取消请求,而不是流式传输一些您可能不想要的长响应。当您调用 res.text() 时,响应正文被缓冲并在承诺解决时提供给您的代码。
  • @Zmalski(续)因此,一旦您将响应作为文本,您就可以解析它的 XML,如您所说。您可以使用生成的Document,就像使用普通 JavaScript 中的 Document 一样。所以,你可以做document.querySelector('label') 或任何你需要的。
【解决方案3】:

解决这个问题的最佳方法应该是使用承诺或回调。 如果你真的想避免承诺或回调(虽然我不知道为什么......) 你可以试试计数器。

    let processCount = 0;
    
    // Increasing the processCount in getXML callback method
    function getXML(term, fromId) {
        var url = someURL;
        $.ajax({
            url: url,
            dataType: "xml",
            success: function(result) {
                processCount++;
                var outerNodes = process(result, fromId, term);
                cont(outerNodes);
            }
        });

    }

    for (var i = 0; i < outerNodes.length; i++) {
        var node = outerNodes.pop();
        getXML(node["label"], node["id"]);
    }
    while (processCount < outerNodes.length) {
        // do nothing, just wait'
    } 

    draw(nodes, edges);

【讨论】:

  • 你回调全部成功执行了吗?
  • 我不明白这个问题,抱歉。
  • 没关系,看看布拉德的答案,它应该是您场景的最佳答案。
【解决方案4】:

如果经过多次测试,你知道它永远不会超过 5 秒...你可以使用 setTimeout。

function cont(outerNodes) {

        for (var i = 0; i < outerNodes.length; i++) {
            var node = outerNodes.pop();
            getXML(node["label"], node["id"]);
    } 


// Display a 5 second progress bar here

 setTimeout(function(){ draw(nodes, edges); },5000); 

}

【讨论】:

  • 这是一个特别糟糕的答案。加载时间会有很大差异。
  • 不幸的是,这仍然存在多个绘图调用的附加问题,只是超时。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-05
  • 2018-04-06
  • 1970-01-01
  • 1970-01-01
  • 2017-03-19
相关资源
最近更新 更多