为什么我的数组返回"undefined"作为数组元素

3
我正在编写一个Web应用程序,其中需要将坐标保存到AngularJS服务中。我使用Ionic Framework、JavaScript和HTML。
我的问题是,数组本身被正确地显示,而单个元素则被放置为“未定义”。
//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;
  }
})

我的代码示例中的特定行是:
console.log(coords);
console.log(coords[0]);
console.log(JSON.stringify(coords, null, 4));

!(http://imgur.com/a/u33hg)

这些是我的返回值。

就像我说的那样,console.log(coords) 打印出了正确的数组,但如果我想调用 console.log(coords[0]) ,它会返回 "undefined"(与 console.log(JSON.stringify(coords,null,4)); 相同)

有人可以解释一下这个问题,或者(更好的是)给出一个解决方案吗?

@Jason Cust 建议后编辑:

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; }

这是结果:

! http://imgur.com/a/A5hAJ

由于每个坐标再次未定义,我无法为每个坐标使用asyncPush,因此我将整个arr数组添加到var a中,现在它是一个数组中的数组,看起来似乎可以工作。当然,我可以构建一个解决方法,将三维数组保存在二维数组中,但是这就是您想要做的吗?

编辑:尝试将三维数组保存到二维数组中会再次返回未定义的变量

解决方案编辑:所以我最终通过遵循有关承诺的教程来解决我的问题:http://exploringjs.com/es6/ch_promises.html

诀窍是使用承诺包装geocodeAddress函数,并在我的initialize函数中调用承诺上的.then函数,以使两个函数按顺序调用。这是我的代码:

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);
});

无论如何,非常感谢 @Jason Cust 的帮助,在没有太多 JS 知识的情况下寻找解决方案让我头痛不已。

敬礼, Julian


也许 getLatLng 是异步的,有时坐标还没有返回。 - BrTkCa
@黑客人:是的,这就是console.log(coords); 的输出结果。console.log(coords [0])的输出结果是“未定义”。LucasCosta:我也想过,但两者都应该有效,因为console.log(coords);每次都有效,而console.log(coords [0])位于下一行。 - Jule Wolf
那个输出没有任何意义...你能发一张图片吗? - Hackerman
数组输出有效,但无法获取某些索引值是没有意义的。或者数组无效,或者有多个变量具有相同的名称,或者更常见的异步原因等问题。如果没有更多细节,例如getLatLng的完整响应,我们无法帮助您。 - BrTkCa
1
最好创建一个演示来复制问题。 - charlietfl
显示剩余7条评论
1个回答

3

这是由于程序的异步性质和 console.log 本身引起的。

在承诺 p 中,调用了一个未被检查完成时间的异步函数 geocodeAddress,因此流程继续执行 dataService.getLatLng() 的返回值检查(以空数组的形式返回,可视为真值),导致 resolve 被执行,最终进入函数块中的 console.log 行。

由于 console.log 的异步行为和使用从 dataService.getLatLng 返回的数组值的引用,当 console.log(coords) 最终打印到 STDOUT 时,它有机会打印这些值。但由于 console.log(coords[0]) 捕获的是即时值,因此其结果为 undefined,再次被打印到 STDOUT。对于 JSON 输出也是如此,因为该操作是同步的。

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; }


谢谢您的回答!您能进一步解释一下函数asyncPush(a, val, cb)的用法吗?我不太明白它为什么会做这样的事情,以及为什么它可以帮助我解决我的问题。 - Jule Wolf
@JuleWolf 中的 asyncPushgeocodeAddress 等同,它们都使用异步调用。通过提供一个机制来知道异步操作何时完成(通过 cb 参数),调用它的代码将知道何时继续其操作。 - Jason Cust
谢谢您的解释。我会尽快尝试并发布您的解决方案的结果。 - Jule Wolf
@JuleWolf 你能解决你的问题吗?如果是的话,本答案有帮助,请记得标记为已解决,这样其他用户就会知道。 :) - Jason Cust
1
@JuleWolf :) 你的“解决方案编辑”实际上就是我在示例代码中演示的内容。通过将geocode包装在一个Promise中并返回该Promise,调用函数现在将知道何时完成。无论如何,很高兴你最终到达了同样的位置。祝你前进顺利,但我仍然建议深入学习异步编程以及Promise如何帮助管理它。 - Jason Cust
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接