1

I feel foolish for asking something so trivial, but I really want the best-practices answer (I'm not looking for a "setTimeout" solution unless nothing else is possible -- though I doubt that is the case).

The quick over-view: I have an array which I want to push to from within a callback. After I've populated the array I then want to use it, outside of the callbacks.

The practical use: I have an array of cities, I want to geocode them with Google's API and populate an array with all of the resulting LatLng's. Later I will create marker objects with them, add them to a clusterer, whatever.

coder = new google.maps.Geocoder();
$places = ['Truro, NS', 'Halifax, NS', 'Sydney, NS', 'Dartmouth, NS'];
all_the_pins = Array();
for(i in $places){
    var $place = $places[i];
    coder.geocode({address:$place}, function(res, stat){
        switch(stat){
            case 'OK':
                all_the_pins.push(res[0].geometry.location);
                break;
        }
    });
}
console.log(all_the_pins);

EDIT: to clarify the issue: The problem isn't a question of scope or whether or not the all_the_pins variable is global or not, if you were to examine all_the_pins within the callback you will see that it is the same variable (which is being pushed to). The problem is that because the pushes happen within the callback they don't happen before the console.log is run below.

tj1
  • 11
  • 3
  • What is the question? Your locations will accumulate in the `all_the_pins` array. If you declare that array globally, it will be available globally. – jfriend00 May 30 '12 at 00:42
  • Simply declare your array as a global var? `var all_the_pins=[];` in the top of the document. It'll be accessible in all scopes. – Fabrício Matté May 30 '12 at 00:43
  • I agree, I don't understand the question... – Jonathan May 30 '12 at 00:44
  • IMO he is asking for how to make an array accessible outside of a function (and by other functions), therefore a global var or passing parameters should be the solution. – Fabrício Matté May 30 '12 at 00:44
  • the problem isn't the scope of `all_the_pins`, it's that because the pushes are inside of the callback which depend on the function being executed, which results in the `console.log` being called first. – tj1 May 30 '12 at 00:48
  • Oh I see, you have multiple calls to `geocode`. Using a counter or `if` to determine when the last call has finished inside your callback then calling your `console.log` or other function could be a solution. – Fabrício Matté May 30 '12 at 00:51
  • OK I see a lot of good answers using iterators and what not. Those all will work, I just can't help but feel there is a more object-oriented way of accomplishing this, I'll sleep on it. And yes, sorry for the for...in, it was originally an object but as I went on that made less and less sense. My bad. – tj1 May 30 '12 at 00:58

3 Answers3

1

Since your question is not clear, I'll hazard a guess that you want to process the all_the_pins array when all your geocode calls are done. Because the geocode function call is asynchronous, you have to keep track of when all geocode calls have completed and then you can use the final array.

If so, you can do that like this:

var coder = new google.maps.Geocoder();
var $places = ['Truro, NS', 'Halifax, NS', 'Sydney, NS', 'Dartmouth, NS'];
var all_the_pins = Array();
var remaining = $places.length;
for (var i = 0, len = $places.length; i < len; i++)
    var $place = $places[i];
    coder.geocode({address:$place}, function(res, stat){
        switch(stat){
            case 'OK':
                all_the_pins.push(res[0].geometry.location);
                break;
        }
        --remaining;
        if (remaining == 0) {
            // all_the_pins data is set here
            // call a function and pass the array to it or put your
            // code here to process it
            console.log(all_the_pins);
        }
    });
}

Note: I also changed to the proper type of for loop for iterating an array. for (var i in xxx) is for iterating properties of an object, not elements of an array.

jfriend00
  • 580,699
  • 78
  • 809
  • 825
1

No, setTimeout is absolutely useless in here. If you have several asynchronous requests, and want to do something when all of them called back, the only possibility is a counter. After the last callback the number of open requests will be down to null; then execute what you want.

var coder = new google.maps.Geocoder();
var places = ['Truro, NS', 'Halifax, NS', 'Sydney, NS', 'Dartmouth, NS'];
var all_the_pins = [];
for (var i=0; i<places.length)
    coder.geocode({address:places[i]}, function(res, stat){
        if (stat == 'OK')
            all_the_pins.push(res[0].geometry.location);
        else
            all_the_pins.push(stat);

        // counter is length of results array:
        if (all_the_pins.length >= places.length) {
            console.log(all_the_pins);
        }
    });
Bergi
  • 513,640
  • 108
  • 821
  • 1,164
0

Can the rest of your code live inside another callback? I would set up a function that only executes after being called x number of times (i.e. the length of your input array).

coder = new google.maps.Geocoder();
$places = ['Truro, NS', 'Halifax, NS', 'Sydney, NS', 'Dartmouth, NS'];
all_the_pins = Array();

function myCallback(count, totalCount, all_the_pins) {
    if (count != totalCount) return;

    console.log(all_the_pins);
}

count = 1;

for(i in $places){
    var $place = $places[i];
    coder.geocode({address:$place}, function(res, stat){
        switch(stat){
            case 'OK':
                all_the_pins.push(res[0].geometry.location);

                myCallback(count, $places.length, all_the_pins);
                count++;
                break;
        }
    });
}
freejosh
  • 10,828
  • 3
  • 30
  • 46