2

So I have read a lot of the different answers about asynchronous functions on here but I think I am over thinking my problem, or I have been staring at it just to long and I cant figure it out. So your help is greatly appreciated.

So I am parsing a csv file and then trying to get the lat/long through another api. but I cant access the lat/lng outside of the function. below is my code I have commented it to the best of my ability please let me know if there are any questions or a much better way to do this.

Thanks,

var location = [] 
function run() {
    http.get(url, function(res) {
        if(res.statusCode === 200) {
            res.pipe(parse(function(err, data) {
                for(i = 1; i < data.length; i++) {
                    var info = data[i];
                    passLoc = info[6].replace('block ', '')
                    passLoc = passLoc.replace(/ /g, "+")
                    getLoc(passLoc, function(loc) {
                        location.push(loc);
                        //If I console.log(location) here I get all the info I want but.....it is printed 100 times becuase it is printed for each i in data.length
                    })
                }
                console.log(location) // loging this here gives me an empty array           
            }))
        }else {
            console.error('The address is unavailable. (%d)', res.statusCode);
        }
    })
}
function getLoc(x, callback) {
    var url = "http://geodata.alleghenycounty.us/arcgis/rest/services/Geocoders/EAMS_Composite_Loc/GeocodeServer/findAddressCandidates?Street=" + x + "&City=Pittsburgh&State=PA&ZIP=&SingleLine=&outFields=&outSR=4326&searchExtent=&f=pjson";
    http.get(url, function(res) {
        var data = '';
        res.on('data', function(chunk) {
            data += chunk;
        });
        res.on('end', function() {
            var d = JSON.parse(data);
            var obj = d.candidates;
            if(obj != '') {
                var loc = obj[0].location
                    var lat = loc.x
                    var lng = loc.y
                    var location = [lat, lng];
                callback(location)
            } else {
                callback(x);
            }
        });
        res.on('error', function(err) {
            callback("error!")
        });
    });
}
FPcond
  • 581
  • 2
  • 7
  • 21
  • Please show only the relevant piece of code – thefourtheye Mar 01 '15 at 14:21
  • took out the one function that isnt used hopefully that helps – FPcond Mar 01 '15 at 14:23
  • Not just that. Most of your application logic and the comments related to that, may not be related to the actual problem. Please remove them also. Because, most people may not like to read pages of code to find the problem for you. – thefourtheye Mar 01 '15 at 14:24
  • first time I am getting comments about to much code lol but ok trimmed it down hopefully that helps just kept the comment regarding what the error i get is – FPcond Mar 01 '15 at 14:29
  • @thefourtheye any thoughts on the issue? – FPcond Mar 01 '15 at 14:41
  • Please check [this question](http://stackoverflow.com/q/23667086/1903116). Solution to your problem is explained in this. Its too broad to explain in comments anyway. – thefourtheye Mar 01 '15 at 14:41
  • @thefourtheye I see that this is an asynchronous callback which is why the title says so but the problem is when I receive the information it is effected by the for loop so the problem is how do I capture the array and do something with it but not have that run 100 times because it is in the for loop – FPcond Mar 01 '15 at 14:53
  • The logic issue is the same as the linked question -- you're synchronously trying to consume asynchronous data. However, you have multiple async operations instead of just one, so you will need to pass the multiple operations to [`async.parallel`](https://github.com/caolan/async#parallel) and consume the data in `async.parallel`'s callback. – Fabrício Matté Mar 01 '15 at 15:08
  • thank you @FabrícioMatté. I will dig into that lib. Are there ways to do it without requiring another library? – FPcond Mar 01 '15 at 15:25
  • @FPcond yes, but most of the time it is not worth reinventing the wheel. Well, in case you're using io.js or Node.js >= 0.12, you can also use the native [Promises](http://www.html5rocks.com/en/tutorials/es6/promises/). – Fabrício Matté Mar 01 '15 at 15:28

1 Answers1

3

Your code tries to synchronously consume asynchronous data -- you're synchronously trying to access the results (location) before any of the asynchronous operations have finished.

As you have multiple async operations running in parallel, you can make use of async.parallel to aid in controlling the asynchronous flow:

var async = require('async');

function run() {
    http.get(url, function(res) {
        if(res.statusCode === 200) {
            res.pipe(parse(function(err, data) {
                // array of async tasks to execute
                var tasks = [];

                data.slice(1).forEach(function(info) {
                    var passLoc = info[6].replace('block ', '').replace(/ /g, '+');

                    // push an async operation to the `tasks` array
                    tasks.push(function(cb) {
                        getLoc(passLoc, function(loc) {
                            cb(null, loc);
                        });
                    });
                });

                // run all async tasks in parallel
                async.parallel(tasks, function(err, locations) {
                    // consume data when all async tasks are finished
                    console.log(locations);
                });
            }));
        }else {
            console.error('The address is unavailable. (%d)', res.statusCode);
        }
    });
}

Also, for loops don't create a scope, so I've swapped it by a forEach in order to scope the info and passLoc variables inside each iteration.

Here's a slightly more condensed version using ES5's Array#map:

var async = require('async');

function run() {
    http.get(url, function(res) {
        if(res.statusCode === 200) {
            res.pipe(parse(function(err, data) {
                async.parallel(
                    // map data items to async tasks
                    data.slice(1).map(function(info) {
                        return function(cb) {
                            var passLoc = info[6].replace('block ', '').replace(/ /g, '+');
                            getLoc(passLoc, function(loc) {
                                cb(null, loc);
                            });
                        };
                    }),
                    function(err, locations) {
                        // consume data when all async tasks are finished
                        console.log(locations);
                    }
                );
            }));
        } else {
            console.error('The address is unavailable. (%d)', res.statusCode);
        }
    });
}
Community
  • 1
  • 1
Fabrício Matté
  • 69,329
  • 26
  • 129
  • 166
  • perfect thank you. also I didnt know about the difference between for and forEach. what do you mean by 'scope the info and passLoc' – FPcond Mar 01 '15 at 16:18
  • I meant that, since `for` loops don't create a scope, all the functions ("tasks") created inside each iteration would reference the same `info` variable and end up with this problem: [JavaScript closure inside loops](http://stackoverflow.com/q/750486/1331430) – Fabrício Matté Mar 01 '15 at 17:12
  • `forEach`, in the other hand, takes a function callback which is executed immediately (synchronously) for each iteration. As the function callback is executed for each iteration, the function's parameters and variable declarations are scoped inside each iteration, and this solves the issue more cleanly than the aforementioned question's accepted answer. – Fabrício Matté Mar 01 '15 at 17:18