Отправка запроса post в цикл for

Я хотел бы отправить запросы post в цикле. Если я отправляю, например, 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);
            });          
        }
    };

4 ответов


использовать 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)

}

раздел ниже напрямую не связан с вопросом, а скорее с комментариями, связанными с этим ответом.


пример представлен на jsFiddle упомянутый в комментариях и показанный ниже багги и как таковой он ничего не доказывает.

это правда, что этот фрагмент, даже не используя закрытие, дает "Hello Kitty" три раза; на самом деле, если вы замените console.log() метод с an alert() один вы увидите, что он дает "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

как вы могли видеть, у нас ошибка и девять выходов "Hello Kitty" подряд :) прежде чем я изменю функцию выше, давайте посмотрим две важные вещи

первый

onreadystatechange событие хранит функцию или ссылку, которая будет вызываться автоматически каждый раз, когда readyState изменения свойств, в то время как status свойство содержит состояние объекта XMLHttpRequest.

readyState возможные значения свойств

  • 0: запрос не инициализирован
  • 1: установлено соединение с сервером
  • 2: запрос получен
  • 3: обработка запроса
  • 4: запрос завершен, и ответ готов

status возможные значения свойств

  • 200: OK
  • 404: страница не найдена

второй

как я сказал в комментариях, jsfiddle.net не является надежным для тестирования асинхронных фрагментов без небольшие изменения. Другими словами,GET метод должен быть изменен на POST и url свойство должно быть изменено на эту ссылку /echo/html/ (для больше вариантов взгляните на документация jsFiddle)

теперь давайте изменим пример из приведенного выше (и следуйте комментариям в коде)

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

1-й выход: / / шесть выходов

(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-й выход: // шесть выходов

(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 были yield. Но значение i остались прежними.

3-й выход: / / после раскомментирования строк для 3-го выхода, показанного выше в примере (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 была сохранена. Но это все еще неверно, так как есть шесть выходов, а нам нужно только три. Поскольку нам не нужны все значения readyState или status свойства XMLHttpRequest объект, раскомментируем две строки, необходимые для четвертого вывода

4-й выход: / / после раскомментирования строк для 4-го выхода, показанного выше в примере (b) - наконец, три выхода

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

наконец, это должна быть правильная версия сниппета и это то, что нам нужно.

еще один всемогущий, всемогущий механизм (как я образно сказал) будет bind() функция, которую я не предпочитаю, так как она медленнее закрытия.


Извините, я не работаю с 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);
});

вы пытаетесь использовать переменную, url внутри for-loop.

если вы не используете закрытие внутри петли, только последнее значение вашего for сделал это $http.post звонок.
Закрытие внутренних петель может быть хитрым зверем. См. этот вопрос JavaScript закрытие внутри петли – простой практический пример и google для получения дополнительной теории / деталей.

ваш код должен быть скорректирован в чем-то вроде это:

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: дополнительная ссылка
У меня была очень похожая проблема не так давно, и вы можете прочитать об этом здесь: угловой JS - $q.all () отслеживание индивидуального прогресса загрузки


это явно проблема закрытия. Подробнее здесь

также предлагается использовать $resource через $http. (ng-resource).

Проверьте пример для публикации в цикле for, используя ресурс.

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

          lsProductionService.post({}, postSuccessCallback); 
      }