【问题标题】:Sending post request in for loop在 for 循环中发送 post 请求
【发布时间】:2014-09-06 12:57:54
【问题描述】:

我想循环发送帖子请求。 例如,如果我连续发送 2 个请求,则只有最后一个请求真正进行了回调。

我做错了什么?

this.assignAUnits = function(){
        var currentIncidentId = this.incident.incidentId;
        for (var i=0; i< this.selectedAvailableUnits.length; i++){
            var unit = this.selectedAvailableUnits[i];
            var unitId = unit.unitId;

            var url = '/incident/' + currentIncidentId + '/assignUnit/' + unitId

            $http.post(url).then(function(response) {
               DOING SOMETHING

            }, function(error) {
                alert(error);
            });          
        }
    };

【问题讨论】:

  • 你可以在浏览器的网络选项卡中查看,真的只发送一个请求吗??
  • @mr-question 请花点时间查看答案并选择正确的答案

标签: javascript angularjs for-loop http-post


【解决方案1】:

使用closure。让我给你看一个简单的例子

// JavaScript on Client-Side
window.onload = function() {
    var f = (function() {
        for (i = 0; i < 3; i++) {
            (function(i){
                var xhr = new XMLHttpRequest();
                var url = "closure.php?data=" + i;
                xhr.open("GET", url, true);
                xhr.onreadystatechange = function () {
                    if (xhr.readyState == 4 && xhr.status == 200) {
                        console.log(xhr.responseText); // 0, 1, 2 
                    }
                };
                xhr.send();
            })(i);
        }
    })();
};

// Server-Side (PHP in this case)
<?php 
    echo $_GET["data"];
?>

在你的情况下......用闭包包装异步调用/函数

for (var i=0; i< this.selectedAvailableUnits.length; i++) {

    (function(i) {    // <--- the catch

        var unit = this.selectedAvailableUnits[i];
        var unitId = unit.unitId;
        var url = '/incident/' + currentIncidentId + '/assignUnit/' + unitId
        $http.post(url).then(function(response) {
            // DOING SOMETHING
        }, function(error) {
            alert(error);
        });

    })(i);    // <---- (the i variable might be omitted if it's not needed)

}

以下部分与问题没有直接关系,而是与与此答案相关的 cmets。


在 cmets 中提到的jsFiddle 上提交的示例如下所示是错误的,因此它不能证明任何事情。

确实,这个 sn-p,即使不使用闭包,也会产生 3 次“Hello Kitty”;实际上,如果您将 console.log() 方法替换为 alert() 方法,您会看到它产生了 6、9 甚至 12 次“Hello Kitty”。那么,到底是怎么回事;)怎么可能在三个迭代的循环中弹出警报窗口六、九或十二次?!

// your example (a)                                   // my comments 
//
var f = (function() {
    for (i = 0; i < 3; i++) {
        //(function(){                                // this way you can't have multiple scopes 
            var xhr = new XMLHttpRequest();
            var url = "closure.php?data=your-data";   // use /echo/html/ for testing on jsfiddle.net
            xhr.open("GET", url, true);               // use POST for testing on jsfiddle.net 
            xhr.onreadystatechange = function () {    // this way you might catch all readyStage property values
                callback();                           // this way the callback function will be called several times
            };
            xhr.send();
        //})();
    }
})();

var callback = function() {
    console.log("Hello Kitty"); // or use alert("Hello Kitty");
};

输出:

GET http://fiddle.jshell.net/_display/closure.php?data=your-data 404 (NOT FOUND) 
(9) Hello Kitty

如您所见,我们有一个错误和连续 9 个“Hello Kitty”输出 :) 在我更改上面的函数之前,让我们看看 两件重要的事情

第一

onreadystatechange 事件存储一个函数或一个引用,每次readyState 属性更改时自动调用,而status 属性保持 XMLHttpRequest 对象的状态。

readyState 属性可能值

  • 0:请求未初始化
  • 1:建立服务器连接
  • 2:收到请求
  • 3:处理请求
  • 4:请求完成,响应准备就绪

status属性可能值

  • 200:好的
  • 404:找不到页面

第二

正如我在 cmets 中所说,jsfiddle.net 在不进行一些更改的情况下测试异步 sn-ps 是不可靠的。换句话说,GET 方法应该更改为POSTurl 属性必须更改为此链接/echo/html/(更多选项请查看jsFiddle documentation

现在,让我们从上面的示例中更改(并遵循代码中的 cmets)

// corrected example (b)
//
var f = (function() {
    for (i = 0; i < 3; i++) {
        //(function(i){                                              // uncomment this line for the 3rd output                               
            var xhr = new XMLHttpRequest();
            var url = "/echo/html";
            var data = "data";
            xhr.open("POST", url, true);
            xhr.onreadystatechange = function () {
                //if (xhr.readyState == 4 && xhr.status == 200) {    // uncomment this line for the 4th output
                    callback(i, xhr.readyState);                     // uncomment this line for the 4th output
                //}
            };
            xhr.send(data);
        //})(i);                                                     // uncomment this line for the 3rd output
    }
})();

var callback = function(i, s) {
    console.log("i=" + i + " - readyState=" + s + " - Hello Kitty");
};

第一个输出://六个输出

(4) i=3 - readyState=1 - Hello Kitty    // four outputs related to readyState value 'server connection established'
    i=3 - readyState=2 - Hello Kitty    // related to readyState value 'request received'
    i=3 - readyState=4 - Hello Kitty    // related to readyState value 'request finished and response is ready'

第二个输出://六个输出

(2) i=3 - readyState=1 - Hello Kitty    // two outputs related to readyState value 'server connection established'
    i=3 - readyState=2 - Hello Kitty    // related to readyState value 'request received'
(3) i=3 - readyState=4 - Hello Kitty    // three outputs related to readyState value 'request finished and response is ready'

没有在示例 (b) 中进行任何更改,我们得到了两个不同的输出。正如你所看到的,不同的 readyState 属性值的不同输出已经产生了。 但i 的值保持不变。

3rd output: // 在上面示例 (b) 中显示的第三个输出的行取消注释后

i=0 - readyState=2 - Hello Kitty        // related to readyState value 'request received'
i=0 - readyState=4 - Hello Kitty        // related to readyState value 'request finished and response is ready'
i=1 - readyState=2 - Hello Kitty        // ...
i=1 - readyState=4 - Hello Kitty        // ... 
i=2 - readyState=2 - Hello Kitty
i=2 - readyState=4 - Hello Kitty

所以,在取消注释保存i 作为参数的函数后,我们看到i 的值已被保存。但这仍然是不正确的,因为有六个输出,我们只需要三个。由于我们不需要XMLHttpRequest 对象的readyStatestatus 属性的所有值,因此我们取消注释第四个输出所需的两行

第 4 个输出:// 取消注释上面示例 (b) 中显示的第 4 个输出的行后 - 最后是三个输出

i=0 - readyState=4 - Hello Kitty
i=1 - readyState=4 - Hello Kitty
i=2 - readyState=4 - Hello Kitty 

最后,这应该是 sn-p 的正确版本,这就是我们所需要的。

另一个全能、无所不能的机制(正如我之前形象地说过的)是 bind() 函数,我不喜欢它,因为它比闭包慢。

【讨论】:

  • hex494D49,您可能是对的,但我怀疑 OP 是否特别受此答案启发。问题先生,问题是(我们认为)您没有告诉我们的// DOING SOMETHING 部分包含变量i。但它在一个回调函数中,直到for 循环完成后才会运行;到那时i==this.selectedAvailableUnits.length,不管它的原始值是多少。在这个闭包中有一个名为i 的新变量,它实际上与原来的i 是分开的;它只在函数中声明,因此它的值不会在循环迭代时重置。
  • @David Knipe 关闭 i 并尝试连续三次回显 echo "Hello Kitty" 而不关闭 :) i 在这种情况下并不重要。
  • 转念一想,我认为我们彼此并不了解。你是说如果函数被一个普通的代码块替换,最后一个带有"Hello Kitty" 的示例将不起作用?我不同意。你为什么这么认为?你有 jsfiddle 来演示吗?
  • @DavidKnipe 好吧,你有不同意的权利 :) 但是再一次,如果你从最后一个例子中去掉这个 (function(){ 和这个 })();Hello Kitty 将只显示一次,不是预期的三倍。你可以测试一下。
  • 好吧,见jsfiddle.net/HbR66。不管有没有这个函数,它的行为都是一样的。
【解决方案2】:

抱歉,我不使用 angularjs,但是使用 jQuery 甚至基本 XMLHttpRequest 发布的这两种方法对我来说效果很好:

<button onclick="sendWithJQuery()">send</button>
<ul id="container"></ul>
<script src="/vendor/bower_components/jquery/dist/jquery.js"></script>
<script>
  //use XMLHttpRequest
  function send(){
      for (var i = 1; i <= 10; i++){
          var xhr = new XMLHttpRequest();
          xhr.open('POST', '/test/' + i);
          xhr.onreadystatechange = function(){
              if (this.readyState != 4){
                  return;
              }
              var li = document.createElement('li');
              li.appendChild(document.createTextNode('client time:' + new Date().toISOString() + ', data: ' + this.responseText));
              container.appendChild(li);
          }
          xhr.send();
      }
  }

  //use jQuery
  function sendWithJQuery(){
      for (var i = 1; i <= 10; i++){
          $.ajax({
          url: '/test/' + i,
          method: "POST",
          statusCode: {
              200: function (data, textStatus, jqXHR) {
                  var li = document.createElement('li');
                  li.appendChild(document.createTextNode('client time:' + new Date().toISOString() + ', data: ' + JSON.stringify(data)));
                  container.appendChild(li);
              },
              500: function (data, textStatus, jqXHR) {
                  alert('Internal server error');
              }
          }
      });
      }
  }
</script>

服务器代码(nodejs):

router.post('/test/:i', function(req, res) {
    var i = req.params.i;
    var t = new Date().toISOString();
    setTimeout(function(){
        res.send({i: i, t: t});
    }, 1000);
});

【讨论】:

    【解决方案3】:

    您正在尝试在for-loop 中使用一个不断变化的变量url

    如果您不在循环内使用闭包,则只有您的 for 的最后一个值才能到达 $http.post 调用。
    循环内部的闭包可能是一个棘手的问题。请参阅此问题JavaScript closure inside loops – simple practical example 并在谷歌上搜索更多理论/细节。

    您的代码将不得不进行如下调整:

    var doPost = function(url) {
    
      $http.post(url).then(
        function(response) {
          // DOING SOMETHING
        },
        function(error) {
          alert(error);
        });
    
    }
    
    this.assignAUnits = function(){
            var currentIncidentId = this.incident.incidentId;
            for (var i=0; i< this.selectedAvailableUnits.length; i++){
                var unit = this.selectedAvailableUnits[i];
                var unitId = unit.unitId;
    
                var url = '/incident/' + currentIncidentId + '/assignUnit/' + unitId
    
                doPost(url)
            }
        };
    

    编辑:附加参考
    不久前我遇到了一个非常相似的问题,您可以在这里阅读:Angular JS - $q.all() tracking individual upload progress

    【讨论】:

      【解决方案4】:

      这显然是一个关闭问题。 阅读更多here

      还建议使用 $resource 而不是 $http。 (ng 资源)。

      查看使用资源在 for 循环中发布的示例。

            for(var i=0; i<$scope.ListOfRecordsToPost.length; i++){       
                var postSuccessCallback = function(postRec) { 
                    console.info('Posted ' + postRec);
                }($scope.ListOfRecordsToPost[i]);
      
                lsProductionService.post({}, postSuccessCallback); 
            }
      

      【讨论】:

        猜你喜欢
        • 2018-06-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-11-13
        • 1970-01-01
        • 2014-02-14
        • 1970-01-01
        相关资源
        最近更新 更多