2014-07-16 10 views
6

Chciałbym wysłać postów w pętli. Jeśli wysyłam na przykład 2 żądania z rzędu, tylko ostatnie żądanie rzeczywiście wykonało wywołanie zwrotne.Wysyłanie żądania pocztowego w pętli for

Co ja robię źle?

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

Czy można sprawdzić w zakładce przeglądarki internetowej, czy rzeczywiście tylko jedno żądanie zostało wysłane? – harishr

+0

@ mr-question proszę nie spiesz się, aby przejrzeć odpowiedzi i wybrać poprawny – domokun

Odpowiedz

4

Użyj closure. Pokażę wam prosty przykład

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

W twoim przypadku ... owinąć asynchroniczne wywołanie funkcji/z zamknięciem

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) 

} 

Poniższa sekcja nie jest bezpośrednio związany z pytaniem ale raczej do komentarzy związanych z tą odpowiedzią.


Przykład składać na jsFiddle wspomniano w komentarzach a poniżej jest błędny i jako taki nie dowodzi niczego.

To prawda, że ​​ten fragment, nawet nie używając zamknięcia, trzy razy powoduje "Hello Kitty"; w rzeczywistości, jeśli zastąpisz metodę console.log() za pomocą metody alert(), zobaczysz, że daje ona "Hello Kitty" sześć, dziewięć lub nawet dwanaście razy. Więc, do cholery, jedziemy;) jak to możliwe, by okno alarmowe pojawiało się sześć, dziewięć lub dwanaście razy w pętli trzech iteracji ?!

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

wyjściowa:

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

Jak można zobaczyć, mamy błąd i dziewięć wyjść „hello kitty” w wierszu :) Zanim zmienić funkcję powyżej Zobaczmy dwa ważne rzecz

Pierwsze

onreadystatechange sklepy zdarzeń funkcja lub odniesienie do automatycznego wywoływania za każdym razem, gdy właściwość readyState zmienia się, podczas gdy właściwość status przechowuje stan obiektu XMLHttpRequest.

readyState własności możliwe wartości

  • 0: nie żądanie inicjowane
  • 1: Połączenie serwera ustalone
  • 2: żądanie odebrane
  • 3: przetwarzanie żądania
  • 4: żądania zakończone i odpowiedź jest gotowy

status własności możliwe wartości

  • 200: OK
  • 404: Nie znaleziono strony

Second

Jak powiedziałem w komentarzach, jsfiddle.net nie jest wiarygodne testowanie fragmentów asynchronicznych bez pewnych zmian. Innymi słowy metoda GET należy zmienić POST a właściwość url musi być zmieniony na ten link /echo/html/ (więcej opcji przyjrzeć jsFiddle documentation)

Teraz zmieńmy przykład z powyższego (i śledzić komentarze w kodzie)

// 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. wyjście: // sześć wyjść

(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-ty wyjście: // sześć wyjść

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

Bez żadnych zmian w przykładzie (b), mamy dwa różne wyjścia. Jak widać, różne wyniki dla różnych wartości właściwości readyState zostały wygenerowane. Ale wartość i pozostała taka sama.

3-ty Wyjście: // po odkomentowanie linii za 3 wyjścia showned powyżej w przykładzie (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 

Tak, po odkomentowanie funkcję, która utrzymuje i jako argumentu, widzimy, że wartość z i został zapisany. Ale to wciąż jest nieprawidłowe, ponieważ istnieje sześć wyników, a potrzebujemy tylko trzech. Ponieważ nie musimy wszystkie wartości readyState lub status właściwość obiektu XMLHttpRequest, niech odkomentowaniu dwie linie potrzebne do czwartego wyjścia

4-ty Wyjście: // po odkomentowanie linie na wyjściu 4rd showned powyżej w Przykład (b) - w końcu trzy wyjścia

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

Wreszcie, powinno to być poprawna wersja fragmencie i to jest to, czego potrzebujemy.

Innym wszechmocnym, wszechpotężnym mechanizmem (jak symbolicznie powiedziałem wcześniej) byłaby funkcja bind(), której nie lubię, ponieważ jest wolniejsza niż zamknięcie.

+0

hex494D49, masz prawdopodobnie rację, ale wątpię, że OP jest szczególnie oświecony tą odpowiedzią. Panie Pytanie, problemem jest (uważamy), że część "// ROBIĆ COŚ", o której nam nie mówiliście, zawiera zmienną "i". Ale jest w funkcji wywołania zwrotnego, która nie działa, dopóki nie zakończy się pętla 'for'; do tego czasu 'i == this.selectedAvailableUnits.length', niezależnie od pierwotnej wartości. W tym zamknięciu znajduje się nowa zmienna o nazwie "i", która jest faktycznie oddzielona od oryginalnego 'i'; jest deklarowana tylko w funkcji, więc jej wartość nie jest resetowana podczas iteracji przez pętlę. –

+0

@David Knipe Wyłącz 'i' i spróbuj echo trzy razy z rzędu' echo "Hello Kitty" 'bez zamknięcia :)' i' nie jest w tym przypadku ważne. – hex494D49

+0

W drugiej chwili myślę, że nie rozumiemy się nawzajem. Czy mówisz, że ten ostatni przykład z '" Hello Kitty "' nie działałby, gdyby funkcja została zastąpiona zwykłym blokiem kodu? Nie zgadzam się. Dlaczego tak myślisz? Czy masz jsfiddle, żeby to pokazać? –

2

Niestety, nie pracuję z angularjs, ale te dwie metody, które Post używając jQuery lub nawet XMLHttpRequest bazie pracę dobrze dla mnie:

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

kod Server (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); 
}); 
1

Próbujesz użyć zmiennej zmieniającej, url, wewnątrz for-loop.

Jeśli nie używasz zamknięcia wewnątrz pętli, tylko ostatnia wartość twojego for doprowadzi do połączenia $http.post.
Zamknięcia w pętlach mogą być trudne. Zobacz to pytanie: JavaScript closure inside loops – simple practical example i google to więcej teorii/szczegółów.

Twój kod będzie musiała zostać dostosowana w coś takiego:

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: dodatkowe odniesienie
miałem bardzo podobny problem nie tak dawno temu, można przeczytać o tym tutaj: Angular JS - $q.all() tracking individual upload progress

1

Jest to zdecydowanie problem zamknięcia. Czytaj więcej here

Zasugerowano także użycie $ zasobów powyżej $ http. (zasoby ng).

Sprawdź przykład, aby opublikować w pętli przy użyciu zasobu.

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

      lsProductionService.post({}, postSuccessCallback); 
     }