jquery deferred in for loop

2019-08-03 09:34发布

问题:

So I have been working on jquery deferred but am having trouble when retrieving data in a loop. The deferred portion seems to only process the data from the final iteration. If there is only one item in the array, it fails as well, so I am not sure what is going on.

I have various city names, and I am trying to get central coordinates for each city from the google maps reverse geocoded

Here is my function that gets the central coordinates:

function getGroupLatLng(groupname){
    var deferred = new $.Deferred();

     geocoder.geocode( { 'address': groupname}, function(results, status) {
      if (status == google.maps.GeocoderStatus.OK) {
              deferred.resolve(results);

          alert(results[0].geometry.location.lat());
      } else {

      }

    });
    return deferred.promise();
}

This is where the function is called, and a div is appended once the result is returned:

var newGroupsLength = newGroups.length;

for (i = 0; i < newGroupsLength; i++) {

    newGroups[i]['counter']=counter;
    var locationName = newGroups[i]['name'];
    counter++;
    alert(locationName);
    $.when(getGroupLatLng(locationName)).then(function(results){
        alert("lat = "+results[0].geometry.location.lat());
        var lat=results[0].geometry.location.lat();
        var lng=results[0].geometry.location.lng();
        console.log(newGroups[i]); //this is running the proper number of times, but it logs the results from the final looped item, 'n' number of times. 

        newGroups[i]['lat']=lat;
        newGroups[i]['lng']=lng;

        var jsonArray=[];
        jsonArray = newGroups[i];
        var template = $('#groupsTemplate').html();
        var html = Mustache.to_html(template, jsonArray);
        $('#groups-container').append(html);
    });
}

The problem I am having is that the deferred loop seems to process the last item in the for loop 'n' number of times, with 'n' being the number of items in the newGroupsLength array. Of course it should process each item one time. If the deferred action is removed, it all works fine.

Sincere thanks for any help. It is greatly appreciated

回答1:

There is two facts that cooperate to give that result:

  1. When writing an object to the log, it's the reference to the object that is written, not a copy of the data. If the object changes after being logged, the log will show the changed data.

  2. The loop will already have completed before the callback function for then is called the first time. That means that i will have the same value for all callbacks, so you put all results in the same object.

So, the values in newGroups[i] will change for each response that is handled, but you will only see the last values in the log because that's what the object contains by the time the log shows it.

To make each iteration in the loop keep the value of i for later when the response arrives, you can use an IIFE (immediately-invoked function expression) to create a local variable for each iteration:

var newGroupsLength = newGroups.length;

for (i = 0; i < newGroupsLength; i++) {

  (function(i){

    newGroups[i]['counter']=counter;
    var locationName = newGroups[i]['name'];
    counter++;
    alert(locationName);
    $.when(getGroupLatLng(locationName)).then(function(results){
        alert("lat = "+results[0].geometry.location.lat());
        var lat=results[0].geometry.location.lat();
        var lng=results[0].geometry.location.lng();

        newGroups[i]['lat']=lat;
        newGroups[i]['lng']=lng;

        console.log(newGroups[i]); // log the object after setting the values

        var jsonArray=[];
        jsonArray = newGroups[i];
        var template = $('#groupsTemplate').html();
        var html = Mustache.to_html(template, jsonArray);
        $('#groups-container').append(html);
    });

  })(i);

}