0

I am using geocoder npm module to convert the address to lat,long. This API reads address from file google.csv using fast csv npm module after that result i.e addresses are passed to getLatLong function to convert to latitude and longitude respectively .Now when I pass latlongArray in getLatLong callback it goes empty.This is getting because of scope.Kindly suggest.

const geocoder   = require('geocoder');
const json2csv   = require('json2csv');
const fs         = require('fs');
const csv        = require('fast-csv'); 
var stream       = fs.createReadStream("google.csv");
var path         = './google.csv';
var async        = require('async');
var responseObj  = {};
var latlongArray = [];

var asyncArray   =[getCsvdata.bind(null, path, responseObj),
                   getLatLong.bind(null, responseObj)];
async.series(asyncArray ,function(err, result){
    if(err){
        console.log(err);
        return err;
    }   
     console.log(JSON.stringify(result));
})

function getCsvdata(path, responseObj, callback){
    var SuccessArray = [];
    var ErrorArray   = [];
    csv.fromPath(path)
        .on('data', function (data) {
            SuccessArray.push(data);
        })
        .on("error", function (data) {
            ErrorArray.push(data);
        })
        .on('end', function () {
            var ResultObject         = {Success: SuccessArray, ErrorList: ErrorArray};
            responseObj.adrressarray = ResultObject;
            callback(null, ResultObject);
        });
 }
function getLatLong(responseObj, callback){
    var responseArray = responseObj.adrressarray; 
    var geocodeArray  = responseArray.Success.slice(1);
    var geoLatLong   = geocodeArray.map(function(x) {
            var addressOfRow = x.toString();
            geocoder.geocode(addressOfRow, function (err, data) {
            if(err){
                return callback(err);
            }
            var latitude    = data.results[0].geometry.location.lat;
            var longitude   = data.results[0].geometry.location.lng;
            var address     = data.results[0].formatted_address;
            var obj         = [{"latitude":latitude,"longitude":longitude, "address":address}];
            latlongArray.push(obj);
        })
    }); 

    return callback(null, latlongArray);
}
Peter Haddad
  • 78,874
  • 25
  • 140
  • 134
art12345
  • 59
  • 8

2 Answers2

1

You are calling the callback too soon (synchronously), while the array is only populated later (asynchronously).

Make these changes:

function getLatLong(responseObj, callback){
    var latlongArray = []; // Define & initialise array here!
    var responseArray = responseObj.adrressarray; 
    var geocodeArray  = responseArray.Success.slice(1);
    geocodeArray.map(function(x) {
        var addressOfRow = x.toString();
        geocoder.geocode(addressOfRow, function (err, data) {
            if(err){
                return callback(err);
            }
            var latitude    = data.results[0].geometry.location.lat;
            var longitude   = data.results[0].geometry.location.lng;
            var address     = data.results[0].formatted_address;
            var obj         = [{"latitude":latitude,"longitude":longitude, "address":address}];
            latlongArray.push(obj);
            // Only call callback when array is complete
            if (latlongArray.length == geocodeArray.length) {
                callback(null, latlongArray);
            }
        })
    }); 
}
trincot
  • 317,000
  • 35
  • 244
  • 286
  • can you please explain how the getLatLong finally returned to async.series?@trincot in the above answer of @Salketer – art12345 Jul 29 '17 at 14:19
  • I think here it is clear, right? I added a comment on the other answer. – trincot Jul 29 '17 at 14:32
  • As you said "Once the tasks have completed, the results are passed to the final callback as an array", but the tasks were of geocode and thier results are passed to async.parallel final callback , From this geocode function there is no callback(err ,result) so how it gets to async.series @trincot – art12345 Jul 29 '17 at 14:39
  • The `async.parallel` implementation will capture the callbacks made by the geocode tasks, collect the data it gets from those, and then call the final callback with that data, which is captured in `async.series`. – trincot Jul 29 '17 at 15:00
  • Last doubt .Where is the call from final callback of async.parallell which is being Captured in async.series@trincot – art12345 Jul 29 '17 at 15:11
  • It is within the implementation of `async.parallel`: it keeps track of all the tasks that have completed (it knows, because it gets all these callbacks), and when it has all it needs, it calls the final callback. Note that this final callback is defined by the second argument passed to `async.series` and passed down to `async.parallel`. – trincot Jul 29 '17 at 15:14
  • Thanks a lot your help is appreciated . @trincot – art12345 Jul 29 '17 at 15:16
  • Funny I had to explain someone else's answer, and not mine :-) You're welcome ;-) – trincot Jul 29 '17 at 15:17
1

You'll want to use async.parallel. Since you are calling multiple geocoder.geocode. Since it is asynchronous, your function returns a value before they have ended.

function getLatLong(responseObj, callback){
    var responseArray = responseObj.adrressarray; 
    var geocodeArray  = responseArray.Success.slice(1);
    var geoLatLongFunctions   = geocodeArray.map(function(x) {
        return function(cb){
            var addressOfRow = x.toString();
            geocoder.geocode(addressOfRow, function (err, data) {
                if(err){
                     cb(err);
                }
                var latitude    = data.results[0].geometry.location.lat;
                var longitude   = data.results[0].geometry.location.lng;
                var address     = data.results[0].formatted_address;
                var obj         = [{"latitude":latitude,"longitude":longitude, "address":address}];
                cb(null,obj);
            });
         };
    }); 
    async.parallel(geoLatLongFunctions,callback);
}

Here, what I've done is make your geocodeArray.map return a function instead. And used async.parallel to execute them. Once all of them has finished, the callback will be called containing the results of all executions.

Salketer
  • 14,263
  • 2
  • 30
  • 58
  • can you please explain how the getLatLong finally returned to async.series?@Salketer – art12345 Jul 29 '17 at 14:08
  • See documentation on [`async.parallel`](https://caolan.github.io/async/docs.html#parallel): *"Once the tasks have completed, the results are passed to the final callback as an array."* – trincot Jul 29 '17 at 14:32