Jule Wolf
Jule Wolf

Reputation: 221

Why does my array return "undefined" as array element

I'm programming a Webapplication in which coordinates have to be saved into a AngularJS service. I'm using Ionic Framework, JavaScript and HTML.

My problem is, that the array itself is getting displayed correctly while the single elements get put out as "undefined".

//Controller für die Map und ihre Funktionen
mapApp.controller('MapCtrl', function($scope, $ionicLoading, dataService) {

//Funktion zur Geocodierung von Adressen Eingabe String
var geocodeAddress = function(geocoder, resultsMap) {

  //Hole Textfeld Input aus Service
  var address = dataService.getAddress();
  //Geocode Funktion analog zu Google Maps API Beispiel
  geocoder.geocode({'address' : address}, function(results, status) {
    if (status === google.maps.GeocoderStatus.OK) {
      resultsMap.setCenter(results[0].geometry.location);
      resultsMap.setZoom(14);

      dataService.setLatLng(results[0].geometry.location.lat(), results[0].geometry.location.lng());

      var marker = new google.maps.Marker({
        map: resultsMap,
        position: results[0].geometry.location,
      });
      //onClick Info Fenster mit Adresse
      var infoString = "Dein Startpunkt: <br>" + results[0].formatted_address;
      var infoWindow = new google.maps.InfoWindow({
        content: infoString
      });
      marker.addListener("click", function() {
        infoWindow.open(resultsMap, marker);
      });
    }
    else {
      alert("Ups, da ist etwas schief gegangen ! Error:  " + status);
    }
  });
}

//Funktion zur Initialisierung der Map
//inklusive Geocoding und Erreichbarkeitspolygonen
var initialize = function() {
      //Route360° stuff
      r360.config.serviceKey = 'KADLJY0DAVQUDEZMYYIM';
      r360.config.serviceUrl = 'https://service.route360.net/germany/';

      var myLatlng = new google.maps.LatLng(48.383, 10.883);
      var mapOptions = {
        center: myLatlng,
        zoom: 8,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      };
      var map = new google.maps.Map(document.getElementById("map"), mapOptions);

      var geocoder = new google.maps.Geocoder();
      var p = new Promise(function (resolve, reject) {

        geocodeAddress(geocoder, map);

        if(dataService.getLatLng()) {
          resolve("Success!");
        }
        else {
          reject("Failure!");
        }
      });
      //console.log(dataService.getLatLng());
      p.then(function() {
        //console.log(dataService.getLatLng());
        var coords = dataService.getLatLng();
        console.log(coords);
        console.log(coords[0]);
        console.log(JSON.stringify(coords, null, 4));

        var time = dataService.getTime();
        var move = dataService.getMove();
        var colorPolygonLayer = new GoogleMapsPolygonLayer(map);
        showPolygons(48.4010822, 9.987607600000047, colorPolygonLayer, time, move);
      });
      $scope.map = map;
}
ionic.Platform.ready(initialize);
});

//Funtkion zum Erstellen und Anzeigen der Erreichbarkeitspolygone
var showPolygons = function(lat, lng, polygonLayer, time, move) {
  //Setzen der Optionen für die Berechnung der Polygone
  var travelOptions = r360.travelOptions();

  //Lat-Lng bestimmen
  travelOptions.addSource({ lat : lat, lng : lng });

  //Zeitintervalle bestimmen
  travelOptions.setTravelTimes(time*60);

  //Fortbewegungsmittel bestimmen
  travelOptions.setTravelType(move);

  r360.PolygonService.getTravelTimePolygons(travelOptions, function(polygons) {
        polygonLayer.update(polygons);
  });
}

//Controller für die Daten
//Eigentlich nur Daten in Service schreiben onClick
mapApp.controller("DataCtrl", function($scope, dataService) {
  $scope.setData = function() {
    var address = document.getElementById("address").value;
    var time = document.getElementById("time").value;
    var move = $scope.move;
    dataService.setData(address,time,move);
  };
});

//Service um Daten zwischen Controllern zu benutzen
mapApp.service("dataService", function() {
  var address;
  var time;
  var move;
  var latlng = [];

  //Adresse, Zeit und Fortbewegungsmittel setzen
  this.setData = function(passed_address, passed_time, passed_move) {
    address = passed_address;
    time = passed_time;
    move = passed_move
  };
  this.setLatLng = function (lat, lng) {
    latlng.push(lat);
    latlng.push(lng);
  };
  //Getter
  this.getAddress = function() {
    return address;
  };
  this.getTime = function() {
    return time;
  };
  this.getMove = function () {
    return move;
  };
  this.getLatLng = function(){
    return latlng;
  }
})

The particular lines in my code sample are

console.log(coords);
console.log(coords[0]);
console.log(JSON.stringify(coords, null, 4));

!(https://i.sstatic.net/w0pUI.jpg)

Those are my return values.

As I said, console.log(coords) prints out the correct array but if I want to call console.log(coords[0]) it returns "undefined" (same as console.log(JSON.stringify(coords,null,4));)

Can someone explain me that issue or can (even better) give me a solution to it ?

Edit after @Jason Cust's suggestion:

var arr = [];
      var p = new Promise(function (resolve, reject) {
        geocodeAddress(geocoder, map);
        asyncPush(arr, dataService.getLatLng(), resolve);
      });

      p.then(function() {
        var a = getArr();
        console.log(a);
        console.log(a[0]);

        var time = dataService.getTime();
        var move = dataService.getMove();
        var colorPolygonLayer = new GoogleMapsPolygonLayer(map);
        showPolygons(48.4010822, 9.987607600000047, colorPolygonLayer, time, move);
      });
      function asyncPush(a, val, cb) {
        setTimeout(function() {
          a.push(val);
          cb();
        } , 0);
      }
      function getArr() {return arr; }

And this is the result:

! https://i.sstatic.net/oAhYq.jpg

I could not use asyncPush for each coordinate since they were undefined again, so I just added the entire arr Array to var a so now it is a array in an array and it seems to work. I can of course build a work-around to save the 3-dimensional array in a 2-dimensional one, but was that what you meant to do ?

Edit: Trying to save the 3-dim array into a 2-dim one returns undefined variables again

Solution Edit: So I could finally solve my problem by following this tutorial about promises: http://exploringjs.com/es6/ch_promises.html

The trick was to wrap the geocodeAddress function with a promise and call the .then function on the promise in my initialize function to make the two functions get called after each other. Here is my code:

var geocodeAddress = function(geocoder, resultsMap) {
  return new Promise(function(resolve, reject) {
    //Hole Textfeld Input aus Service
    var address = dataService.getAddress();
    //Geocode Funktion analog zu Google Maps API Beispiel
    geocoder.geocode({'address' : address}, function(results, status) {
      if (status === google.maps.GeocoderStatus.OK) {
        resultsMap.setCenter(results[0].geometry.location);
        resultsMap.setZoom(14);

        dataService.setLatLng(results[0].geometry.location.lat(), results[0].geometry.location.lng());

        var marker = new google.maps.Marker({
          map: resultsMap,
          position: results[0].geometry.location,
        });
        //onClick Info Fenster mit Adresse
        var infoString = "Dein Startpunkt: <br>" + results[0].formatted_address;
        var infoWindow = new google.maps.InfoWindow({
          content: infoString
        });
        marker.addListener("click", function() {
          infoWindow.open(resultsMap, marker);
        });
      }
      if(dataService.getLatLng()) {
        resolve("Success");
      }
      else {
        alert("Ups, da ist etwas schief gegangen ! Error:  " + status);
        reject("Failed");
      }
    });
  });
}

//Funktion zur Initialisierung der Map
//inklusive Geocoding und Erreichbarkeitspolygonen
var initialize = function() {
      //Route360° stuff
      r360.config.serviceKey = 'KADLJY0DAVQUDEZMYYIM';
      r360.config.serviceUrl = 'https://service.route360.net/germany/';

      var myLatlng = new google.maps.LatLng(48.383, 10.883);
      var mapOptions = {
        center: myLatlng,
        zoom: 8,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      };
      var map = new google.maps.Map(document.getElementById("map"), mapOptions);
      var geocoder = new google.maps.Geocoder();

      geocodeAddress(geocoder, map)
      .then(function() {
        var coords = dataService.getLatLng();
        var time = dataService.getTime();
        var move = dataService.getMove();
        var colorPolygonLayer = new GoogleMapsPolygonLayer(map);
        showPolygons(coords[0], coords[1], colorPolygonLayer, time, move);
      });
      $scope.map = map;
}
ionic.Platform.ready(initialize);
});

Anyway, thank you very much @Jason Cust for your help, searching a solution for this without much JS knowledge gave me a huge headache.

Many regards, Julian

Upvotes: 3

Views: 11164

Answers (1)

Jason Cust
Jason Cust

Reputation: 10899

This is due to the async nature of the program and for console.log itself.

Inside the promise p is a call to an async function geocodeAddress that is never checked for when it completes so the process continues on to the check for dataService.getLatLng() returning a value (which it does in the form of the empty array, which is truthy) which causes the resolve to be executed which in turn finally gets into the function block where the console.log lines are.

Due to the async behavior of console.log and the reference of using the array value returned from dataService.getLatLng when console.log(coords) finally prints to STDOUT it has the chance to print the values. But since console.log(coords[0]) grabs the immediate value it is undefined, which again is later printed to STDOUT. The same goes for the JSON output since that operation is sync.

Essentially what the code does now:

var arr = [];

var p = new Promise(function(resolve, reject) {
  asyncPush(arr, 'foo');
  
  if (getArr()) {
    resolve();
  }
});

p.then(function() {
  var a = getArr();
  
  console.log(a);
  console.log(a[0]);
  console.log(JSON.stringify(a, null, 2));
});

function asyncPush(a, val) {
  setTimeout(function() { a.push(val) }, 0);
}
  
function getArr() { return arr; }

Checking to make sure the async waits before continuing:

var arr = [];

var p = new Promise(function(resolve, reject) {
  asyncPush(arr, 'foo', resolve);
});

p.then(function() {
  var a = getArr();
  
  console.log(a);
  console.log(a[0]);
  console.log(JSON.stringify(a, null, 2));
});

function asyncPush(a, val, cb) {
  setTimeout(function() { a.push(val); cb(); }, 0);
}
  
function getArr() { return arr; }

Upvotes: 3

Related Questions