7

I would like to send post requests in loop. If i send for example 2 requests in a row only the last request really made the callback.

What am i do wrong?

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);
            });          
        }
    };
Aviade
  • 2,057
  • 4
  • 27
  • 49

4 Answers4

7

Use a closure. Let me show you a simple example

// 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"];
?>

In your case... wrap the asynchronous call/function with a closure

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)

}

The section below is not directly related to the question but rather to the comments related to this answer.


The example submitted on jsFiddle mentioned in the comments and shown below is buggy and as such it doesn't prove anything.

It's true that this snippet, even not using a closure, yields 'Hello Kitty' three times; actually, if you replace console.log() method with the an alert() one you will see that it yields 'Hello Kitty' six, nine or even twelve times. So, what the hell is going one ;) how it's possible to get the alert window popping up six, nine or twelve times within a loop of three iterations?!

// 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");
};

Output:

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

As you could see, we've got an error and nine 'Hello Kitty' outputs in a row :) Before I change the function above let's see two important thing

First

onreadystatechange event stores a function or a reference to be called automatically each time the readyState property changes while status property holds the status of the XMLHttpRequest object.

readyState property possible values

  • 0: request not initialized
  • 1: server connection established
  • 2: request received
  • 3: processing request
  • 4: request finished and response is ready

status property possible values

  • 200: OK
  • 404: Page not found

Second

As I said in the comments, jsfiddle.net isn't reliable for testing asynchronous snippets without some changes. In other words the GET method should be changed to POST and the url property must be changed to this link /echo/html/ (for more options take a look at jsFiddle documentation)

Now, let's change the example from the above (and follow the comments within the code)

// 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");
};

1st output: // six outputs

(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'

2nd output: // six outputs

(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'

Without any changes made in example (b), we've got two different outputs. As you can see, different outputs for different readyState property values has been yield. But the value of i remained the same.

3rd output: // after uncommenting the lines for the 3rd output showned above in the example (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

So, after uncommenting the function which holds i as an argument, we see that the value of i has been saved. But this is still incorrect since there are six outputs and we need only three. As we don't need all the values of readyState or status property of the XMLHttpRequest object, let's uncomment the two lines needed for the fourth output

4th output: // after uncommenting the lines for the 4rd output showned above in the example (b) - finally three outputs

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

Finally, this should be the correct version of the snippet and this is what we need.

Another almighty, omnipotent mechanism (as I figuratively said before) would be the bind() function which I don't prefer since it's slower than a closure.

hex494D49
  • 9,109
  • 3
  • 38
  • 47
  • hex494D49, you're probably right but I doubt that OP is particularly enlightened by this answer. Mr. Question, the problem is (we think) that the `// DOING SOMETHING` part you didn't tell us about contains the variable `i`. But it's in a callback function, which doesn't run until after the `for` loop has finished; by then `i==this.selectedAvailableUnits.length`, regardless of its original value. In this closure there's a new variable called `i`, which is actually separate from the original `i`; it's only declared within the function, so its value is not reset on iteration through the loop. – David Knipe Jul 16 '14 at 07:52
  • @David Knipe Take the `i` off and try to echo three times in a row `echo "Hello Kitty"` without a closure :) The `i` isn't important in this case. – hex494D49 Jul 16 '14 at 08:02
  • On second thoughts I don't think we understand each other. Are you saying that that last example with `"Hello Kitty"` wouldn't work if the function was replaced with just an ordinary block of code? I disagree. Why do you think this? Do you have a jsfiddle to demonstrate it? – David Knipe Jul 16 '14 at 23:38
  • @DavidKnipe Well, it's your right to disagree :) But once again, if you take off this `(function(){` and this `})();` from the last example, `Hello Kitty` will be shown only once, not three times as expected. You may test it. – hex494D49 Jul 16 '14 at 23:54
  • Alright then, see http://jsfiddle.net/HbR66/ . It behaves exactly the same with or without the function. – David Knipe Jul 17 '14 at 07:48
  • Dear @DavidKnipe you can't test asynchronous snippets on jsFiddle - it's unreliable for this kind of thing; then, I see you've commented the wrong closure - you should comment the line below the `for loop` and the line below the `send method`; finally, the code on jsFiddle doesn't even execute. C'mon man :) just upload this snippet on a proper (even local) server, make a php/c#/java file with whatever content you want, even one line of code and you'll see the results. – hex494D49 Jul 17 '14 at 08:40
  • You're right that I meant to comment the other bit. Like so: http://jsfiddle.net/kKzT5/1/ . I've also removed the `if` condition in case the condition is false on your browser. It runs for me (3 times); see console logs for output. I've also tried running it from local .html file; that works too. Now that I've jumped through your hoops, maybe you could try it yourself. More importantly, explain what you think is going on. What's an "almighty or omnipotent mechanism" and why are they useful? – David Knipe Jul 17 '14 at 19:57
  • You miss the point. I only removed `if()` because I wasn't sure what happened when you ran it, and wanted to make sure handler ran at least once. However many times it runs, the point is: it runs just as many times without nesting in a function. If you insist on less "buggy" version, try an earlier draft: http://jsfiddle.net/kKzT5/ . I always agreed that the enclosing function is needed to scope `i`, but the original `"Hello Kitty"` with no `i` in output doesn't need it. Do you now agree that the enclosing function doesn't make callback run more times, as you claimed in your second comment? – David Knipe Jul 19 '14 at 10:01
2

Sorry, I don't work with angularjs, but these two methods that post using jQuery or even base XMLHttpRequest work well for me:

<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>

Server code (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);
});
Viktor Kireev
  • 1,200
  • 1
  • 8
  • 17
1

You're trying to use a changing variable, url, inside a for-loop.

If you don't use a closure inside a loop, only the last value of your for will made it to the $http.post call.
Closures inside loops can be a tricky beast. See this question JavaScript closure inside loops – simple practical example and google it for more theory/details.

Your code will have to be adjusted in something like this:

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)
        }
    };

Edit: additional reference
I had a very similar issue not long ago, and you can read about it here: Angular JS - $q.all() tracking individual upload progress

Community
  • 1
  • 1
domokun
  • 3,013
  • 3
  • 29
  • 55
1

Its clearly a closure issue. Read more here

Also it's suggested using $resource over $http. (ng-resource).

Check out the example to post in for loop using resource.

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

          lsProductionService.post({}, postSuccessCallback); 
      }
Prashanth
  • 59
  • 5