0

I am a Java developer student and I have a problem understanding how to properly write asynchronous functions in Javascript. I read a lot of tutorials but I don't find a good way to implement proper asynchronous methods all the time.

I am making an app using the Google Maps API and in a method I want to use the directionsService API in order to retrieve the overview_polyline value out of the result object. The method does what it should but it returns an undefined object before to receive the data.

The method looks like this :

"start and "end" variables are both Google Markers

  async createPolyline(startEndPointsArray){
    //Creates a polyline
    const directionsService = new google.maps.DirectionsService();
    const request = {
      origin: this.start.getPosition(),
      destination: this.end.getPosition(),
      travelMode: google.maps.TravelMode.DRIVING,
      unitSystem: google.maps.UnitSystem.METRIC
    };

   const getData = await directionsService.route(request,
      function (result, status) {
        if (status === "OK") {
          console.log(result.routes[0].overview_polyline)
          return result.routes[0].overview_polyline;
        } else {
          window.alert("Directions request failed due to " + status);
        }
      });
    getData.then(data => {return data})
  }

getData remains null so I guess the problem is within the function

and , for reference, it is called from this method that is also synchronous

 async setChargingStationsMarkers(startEndPointsArray) {
   
    const polyline = await this.createPolyline(startEndPointsArray);
    console.log(polyline);

[some additional code fetching a json response from an external website...]

code snippet (from fiddle in comments)

let map;
let mapCenter = { lat: 59.428, lng: 24.76};
let start;
let end;
let chargingPointsMarkers = [];
let markerArray = [];
let stopoverMarkers = []
let vehicle1 = {capacity: 33, status: 33, consumption: 6.6666} //1KW = 6.6666 Km; Capacity in KM = status*consumption;

function initMap(listener) {
    //Create the map, the DirectionsService, the DirectionsRenderer and an eventListener for the GO button
    //If I chose to implement a detailed steps display it would also be created here
    const directionsService = new google.maps.DirectionsService();

    const mapOptions = {
        center: mapCenter,
        zoom: 7,
    }
    map = new google.maps.Map(document.getElementById("map"), mapOptions);

    const directionsRenderer = new google.maps.DirectionsRenderer({map: map});

    //const stepDisplay = new google.maps.InfoWindow();

    const geocoder = new google.maps.Geocoder();
    document.getElementById("submit").addEventListener("click", () => {
        launcher(geocoder, directionsRenderer, directionsService);
    });
}


async function launcher(geocoder, directionsRenderer, directionsService){
    //the method is used to be launched by the eventListener
    //it sets up the start and end points, fetches the EV markers and launches the route calculation process though a callback function
    resetMarkers();
    const startEndPointsArray =  await setupRoutingProcess(geocoder);
    await callbackHandler(startEndPointsArray,directionsRenderer,
        directionsService, calculateAndDisplayRoute);
}

function setMapOnAll(map){
    // Sets the map on all markers in the array.
    for (let i = 0; i < markerArray.length; i++) {
        markerArray[i].setMap(map);
    }
}

function clearMarkers() {
    // Removes the markers from the map, but keeps them in the array.
    setMapOnAll(null);
}

function resetMarkers(){
    // Pushes all visible markers to a same array,
    // launches the different reset processes and
    // deletes all markers in the arrays by removing references to them.
    for (let i = 0; i < chargingPointsMarkers.length; i++) {
        markerArray.push(chargingPointsMarkers[i])
    }
    chargingPointsMarkers = [];

    for (let j = 0; j < stopoverMarkers.length; j++) {
        markerArray.push(stopoverMarkers[j])
    }
    stopoverMarkers = [];

    clearMarkers();

    markerArray = []
}

async function setupRoutingProcess(geocoder){
    //launches the setGeocodeAddress method for both start and end points and stores them in an array
    start = await setGeocodeAddress(geocoder, map, "start");
    end = await setGeocodeAddress(geocoder, map, "end");
    let startEndPointsArray = [start];
    startEndPointsArray.push(end);
    return startEndPointsArray;

}

async function setGeocodeAddress(geocoder, resultsMap, elementId) {
    //Retrieve the addresses (strings) from the html text boxes and uses Geocoder to Google Markers objects.
    //it pushes those markers in an array later used to delete the markers on the map
    const address = document.getElementById(elementId).value;
    return new Promise(resolve => geocoder.geocode({address: address},
        (results, status) => {
            if (status === "OK") {
                resultsMap.setCenter(results[0].geometry.location);
                const marker = new google.maps.Marker({
                    map: resultsMap,
                    position: results[0].geometry.location,
                    title: elementId,
                });
                resolve(marker)
                markerArray.push(marker);
            } else {
                alert("Trip Route finder was not successful for the following reason: " + status);
            }
        }));
}

async function callbackHandler (startEndPointsArray,
                                directionsRenderer,
                                directionsService,
                                calculateAndDisplayRoute){

    let jsonChargingPoints = await setChargingStationsMarkers(startEndPointsArray, directionsRenderer,
        directionsService, calculateAndDisplayRoute);


    await createChargerPointMarkers(jsonChargingPoints)

    calculateAndDisplayRoute(
        directionsRenderer,
        directionsService,
        jsonChargingPoints
    );
}

async function setChargingStationsMarkers(startEndPointsArray, directionsRenderer,
                                          directionsService, calculateAndDisplayRoute) {
    //Creates an encoded polyline to be passed as an Url argument to limit the results
    //fetches the EV Charging Points as Json response
    const polyline = await createPolyline(startEndPointsArray);
    console.log(polyline);
    const baseUrl = 'https://api.openchargemap.io/v3/poi/?output=json&maxresults=200&includecomments=true';
    const queryUrl = baseUrl + '&polyline=' + polyline + '&distance=50';
    let data  = await fetch(queryUrl)
        .then((response) => response.json())
        .then((data) => {return data})
    return data;
}

  async function createPolyline(startEndPointsArray){
    //Creates a polyline
    const directionsService = new google.maps.DirectionsService();
    const request = {
      origin: start.getPosition(),
      destination: end.getPosition(),
      travelMode: google.maps.TravelMode.DRIVING,
      unitSystem: google.maps.UnitSystem.METRIC
    };

   const getData = await directionsService.route(request,
      function (result, status) {
        if (status === "OK") {
          console.log(result.routes[0].overview_polyline)
          return result.routes[0].overview_polyline;
        } else {
          window.alert("Directions request failed due to " + status);
        }
      });
    console.log(getData)
    getData.then(data => {return data})
  }

function createChargerPointMarkers(jsonChargingPoints) {
    //Loop through the Json response and launch the PlaceMarkers function
    for (let x = 0; x < jsonChargingPoints.length; x++) {
        const LatLng = new google.maps.LatLng(parseFloat(jsonChargingPoints[x].AddressInfo.Latitude), parseFloat(jsonChargingPoints[x].AddressInfo.Longitude));
        placeMarker(LatLng);
    }
}

function placeMarker(location) {
    //Convert the Json response elements to Google Markers, places them on the Map and pushes them to an array.
    let marker = new google.maps.Marker({
        position: location,
        map,
        icon: {
            path: google.maps.SymbolPath.BACKWARD_CLOSED_ARROW,
            scale: 3,
        },
        draggable: false,
    });
    markerArray.push(marker)
    chargingPointsMarkers.push(marker)
}

async function calculateAndDisplayRoute(
    directionsRenderer,
    directionsService,
    jsonChargingPoints,
    stepDisplay,
    map) {

    if (!compareVehicleCapacityToDistance(vehicle1, start)) {
        setChargeCheckpoint(vehicle1)
    }

    directionsService.route(setRequest(),
        function (result, status) {
            if (status === "OK") {
                directionsRenderer.setDirections(result);
                // showSteps(result, markerArray, stepDisplay, map);
            } else {
                window.alert("Directions request failed due to " + status);
            }
        });
}

function setRequest(){
    //prepares the request sent to the Directions service
    let stopovers = [];
    for (let x = 0; x < stopoverMarkers.length; x++){
        let latLng = stopoverMarkers[x].getPosition();
        let waypoint = {
            location: latLng,
            stopover: true
        };
        stopovers.push(waypoint)
    }

    const request = {
        origin: start.getPosition(),
        destination: end.getPosition(),
        waypoints: stopovers,
        travelMode: google.maps.TravelMode.DRIVING,
        unitSystem: google.maps.UnitSystem.METRIC
    };
    return request;
}

function compareVehicleCapacityToDistance(vehicle, p1){
    //Checks if the distance to destination is greater than the vehicle capacity
    if (calculateDistance(p1, end) > (vehicle.status*vehicle.consumption)){
        return false
    }return true;
}

function setChargeCheckpoint(vehicle){
    //launches the function selecting the closest marker to destination
    //Setting a marker of the selected marker on the map (might be redundant)
    //Pushes it to markerArray for later deletion (might be redundant)
    //Pushes it to stopoverMarkers to be used by the Directions service to setup a route
    let waypoint = selectMarkerClosestToDestination(vehicle);
    const waypointLocation = waypoint.getPosition();
    const marker = new google.maps.Marker({
        position: waypointLocation,
        stopover: true,
        draggable: false,
        title: "EV charging stopover"
    });
    markerArray.push(marker)
    stopoverMarkers.push(marker)
}



function selectMarkerClosestToDestination(vehicle) {
    //Selecting the closest marker to destination as long as it is not out of the vehicle capacity range
    //CURRENTLY BUGGED
    let waypoints = chargingPointsMarkers;

    for (let x = waypoints.length -1; x >= 0; x--) {
        if(calculateDistance(waypoints[x], start) > (vehicle.status*vehicle.consumption)){
            waypoints.splice(x, 1)
        }
    }

    for (let x = waypoints.length - 1; x > 0; x--) {
        if (calculateDistance(waypoints[x], end) > (calculateDistance(waypoints[x-1], end))) {
            waypoints.splice(x, 1);
        } else {
            waypoints.splice(x - 1, 1);
        }
    }
    return waypoints[0];
}

function calculateDistance(p1, p2) {
    //Uses the Google geometry library to calculate distance between two Markers
    let a = p1.getPosition();
    let b = p2.getPosition();

    let distance = (google.maps.geometry.spherical.computeDistanceBetween(a, b) / 1000).toFixed(2);
    return distance;
}

function showSteps(directionResult, stepDisplay, map) {
    // For each step, place a marker, and add the text to the marker's infowindow.
    // Also attach the marker to an array so we can keep track of it and remove it
    // when calculating new routes.
    //NOT CURRENTLY IMPLEMENTED/USED
    const myRoute = directionResult.routes[0].legs[0];

    for (let i = 0; i < myRoute.steps.length; i++) {
        const marker = (markerArray[i] =
            markerArray[i] || new google.maps.Marker());
        marker.setMap(map);
        marker.setPosition(myRoute.steps[i].start_location);
        attachInstructionText(
            stepDisplay,
            marker,
            myRoute.steps[i].instructions,
            map
        );
    }
}

function attachInstructionText(stepDisplay, marker, text, map) {
    google.maps.event.addListener(marker, "click", () => {
        // Open an info window when the marker is clicked on, containing the text
        // of the step.
        //NOT CURRENTLY IMPLEMENTED/USED
        stepDisplay.setContent(text);
        stepDisplay.open(map, marker);
    });
}
/* Always set the map height explicitly to define the size of the div
       * element that contains the map. */
#map {
    height: 90%;
}

/* Optional: Makes the sample page fill the window. */
html,
body {
    height: 100%;
    margin: 0;
    padding: 0;
}

#floating-panel {
    position: absolute;
    top: 10px;
    left: 25%;
    z-index: 5;
    background-color: #fff;
    padding: 5px;
    border: 1px solid #999;
    text-align: center;
    font-family: "Roboto", "sans-serif";
    line-height: 30px;
    padding-left: 10px;
}

#warnings-panel {
    width: 100%;
    height: 10%;
    text-align: center;
}
<!DOCTYPE html>
<html>
<head>
    <title>EV Trip Route Finder</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
    <script
            src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap&libraries=&v=weekly"
            defer
    ></script>
</head>
<body>
<div id="floating-panel" >
    <b>Start: </b>
    <input id="start" type="text" value="Nantes">
    <b>End: </b>
    <input id="end" type="text" value="Paris">
    <input id="submit" type="button" value="GO" />
</div>
<div id="map"></div>
<div id="warnings-panel"></div>
</body>
</html>
geocodezip
  • 147,872
  • 13
  • 194
  • 222
Reivilo85k
  • 43
  • 6

1 Answers1

1

The directions service is asynchronous (like the geocoder is). The posted code handles the two services differently. Handling the directions service the same way as the geocoder solves the problem.

async function createPolyline(startEndPointsArray) {
  //Creates a polyline
  const directionsService = new google.maps.DirectionsService();
  const request = {
    origin: start.getPosition(),
    destination: end.getPosition(),
    travelMode: google.maps.TravelMode.DRIVING,
    unitSystem: google.maps.UnitSystem.METRIC
  };
  return new Promise(resolve => directionsService.route(request,
    function(result, status) {
      if (status === "OK") {
        resolve(result)
      } else {
        window.alert("Directions request failed due to " + status);
      }
    })
  );
}

calling it like this:

const polyline = await createPolyline(startEndPointsArray);
console.log(polyline);

proof of concept fiddle

code snippet:

let map;
let mapCenter = {
  lat: 59.428,
  lng: 24.76
};
let start;
let end;
let chargingPointsMarkers = [];
let markerArray = [];
let stopoverMarkers = []
let vehicle1 = {
  capacity: 33,
  status: 33,
  consumption: 6.6666
} //1KW = 6.6666 Km; Capacity in KM = status*consumption;

function initMap(listener) {
  //Create the map, the DirectionsService, the DirectionsRenderer and an eventListener for the GO button
  //If I chose to implement a detailed steps display it would also be created here
  const directionsService = new google.maps.DirectionsService();

  const mapOptions = {
    center: mapCenter,
    zoom: 7,
  }
  map = new google.maps.Map(document.getElementById("map"), mapOptions);

  const directionsRenderer = new google.maps.DirectionsRenderer({
    map: map
  });
  const geocoder = new google.maps.Geocoder();
  document.getElementById("submit").addEventListener("click", () => {
    launcher(geocoder, directionsRenderer, directionsService);
  });
}


async function launcher(geocoder, directionsRenderer, directionsService) {
  //the method is used to be launched by the eventListener
  //it sets up the start and end points, fetches the EV markers and launches the route calculation process though a callback function
  resetMarkers();
  const startEndPointsArray = await setupRoutingProcess(geocoder);
  await callbackHandler(startEndPointsArray, directionsRenderer,
    directionsService, calculateAndDisplayRoute);
}

function setMapOnAll(map) {
  // Sets the map on all markers in the array.
  for (let i = 0; i < markerArray.length; i++) {
    markerArray[i].setMap(map);
  }
}

function clearMarkers() {
  // Removes the markers from the map, but keeps them in the array.
  setMapOnAll(null);
}

function resetMarkers() {
  // Pushes all visible markers to a same array,
  // launches the different reset processes and
  // deletes all markers in the arrays by removing references to them.
  for (let i = 0; i < chargingPointsMarkers.length; i++) {
    markerArray.push(chargingPointsMarkers[i])
  }
  chargingPointsMarkers = [];

  for (let j = 0; j < stopoverMarkers.length; j++) {
    markerArray.push(stopoverMarkers[j])
  }
  stopoverMarkers = [];

  clearMarkers();

  markerArray = []
}

async function setupRoutingProcess(geocoder) {
  //launches the setGeocodeAddress method for both start and end points and stores them in an array
  start = await setGeocodeAddress(geocoder, map, "start");
  end = await setGeocodeAddress(geocoder, map, "end");
  let startEndPointsArray = [start];
  startEndPointsArray.push(end);
  return startEndPointsArray;

}

async function setGeocodeAddress(geocoder, resultsMap, elementId) {
  //Retrieve the addresses (strings) from the html text boxes and uses Geocoder to Google Markers objects.
  //it pushes those markers in an array later used to delete the markers on the map
  const address = document.getElementById(elementId).value;
  return new Promise(resolve => geocoder.geocode({
      address: address
    },
    (results, status) => {
      if (status === "OK") {
        resultsMap.setCenter(results[0].geometry.location);
        const marker = new google.maps.Marker({
          map: resultsMap,
          position: results[0].geometry.location,
          title: elementId,
        });
        resolve(marker)
        markerArray.push(marker);
      } else {
        alert("Trip Route finder was not successful for the following reason: " + status);
      }
    }));
}

async function callbackHandler(startEndPointsArray,
  directionsRenderer,
  directionsService,
  calculateAndDisplayRoute) {

  let jsonChargingPoints = await setChargingStationsMarkers(startEndPointsArray, directionsRenderer,
    directionsService, calculateAndDisplayRoute);


  await createChargerPointMarkers(jsonChargingPoints)

  calculateAndDisplayRoute(
    directionsRenderer,
    directionsService,
    jsonChargingPoints
  );
}

async function setChargingStationsMarkers(startEndPointsArray, directionsRenderer,
  directionsService, calculateAndDisplayRoute) {
  //Creates an encoded polyline to be passed as an Url argument to limit the results
  //fetches the EV Charging Points as Json response
  const polyline = await createPolyline(startEndPointsArray);
  console.log(polyline);
  const baseUrl = 'https://api.openchargemap.io/v3/poi/?output=json&maxresults=200&includecomments=true';
  const queryUrl = baseUrl + '&polyline=' + polyline + '&distance=50';
  let data = await fetch(queryUrl)
    .then((response) => response.json())
    .then((data) => {
      return data
    })
  return data;
}

async function createPolyline(startEndPointsArray) {
  //Creates a polyline
  const directionsService = new google.maps.DirectionsService();
  const request = {
    origin: start.getPosition(),
    destination: end.getPosition(),
    travelMode: google.maps.TravelMode.DRIVING,
    unitSystem: google.maps.UnitSystem.METRIC
  };
  return new Promise(resolve => directionsService.route(request,
    function(result, status) {
      if (status === "OK") {
        console.log(result.routes[0].overview_polyline)
        resolve(result.routes[0].overview_polyline)
      } else {
        window.alert("Directions request failed due to " + status);
      }
    })
    );
}

function createChargerPointMarkers(jsonChargingPoints) {
  //Loop through the Json response and launch the PlaceMarkers function
  for (let x = 0; x < jsonChargingPoints.length; x++) {
    const LatLng = new google.maps.LatLng(parseFloat(jsonChargingPoints[x].AddressInfo.Latitude), parseFloat(jsonChargingPoints[x].AddressInfo.Longitude));
    placeMarker(LatLng);
  }
}

function placeMarker(location) {
  //Convert the Json response elements to Google Markers, places them on the Map and pushes them to an array.
  let marker = new google.maps.Marker({
    position: location,
    map,
    icon: {
      path: google.maps.SymbolPath.BACKWARD_CLOSED_ARROW,
      scale: 3,
    },
    draggable: false,
  });
  markerArray.push(marker)
  chargingPointsMarkers.push(marker)
}

async function calculateAndDisplayRoute(
  directionsRenderer,
  directionsService,
  jsonChargingPoints,
  stepDisplay,
  map) {

  if (!compareVehicleCapacityToDistance(vehicle1, start)) {
    setChargeCheckpoint(vehicle1)
  }

  directionsService.route(setRequest(),
    function(result, status) {
      if (status === "OK") {
        directionsRenderer.setDirections(result);
      } else {
        window.alert("Directions request failed due to " + status);
      }
    });
}

function setRequest() {
  //prepares the request sent to the Directions service
  let stopovers = [];
  for (let x = 0; x < stopoverMarkers.length; x++) {
    let latLng = stopoverMarkers[x].getPosition();
    let waypoint = {
      location: latLng,
      stopover: true
    };
    stopovers.push(waypoint)
  }

  const request = {
    origin: start.getPosition(),
    destination: end.getPosition(),
    waypoints: stopovers,
    travelMode: google.maps.TravelMode.DRIVING,
    unitSystem: google.maps.UnitSystem.METRIC
  };
  return request;
}

function compareVehicleCapacityToDistance(vehicle, p1) {
  //Checks if the distance to destination is greater than the vehicle capacity
  if (calculateDistance(p1, end) > (vehicle.status * vehicle.consumption)) {
    return false
  }
  return true;
}

function setChargeCheckpoint(vehicle) {
  //launches the function selecting the closest marker to destination
  //Setting a marker of the selected marker on the map (might be redundant)
  //Pushes it to markerArray for later deletion (might be redundant)
  //Pushes it to stopoverMarkers to be used by the Directions service to setup a route
  let waypoint = selectMarkerClosestToDestination(vehicle);
  const waypointLocation = waypoint.getPosition();
  const marker = new google.maps.Marker({
    position: waypointLocation,
    stopover: true,
    draggable: false,
    title: "EV charging stopover"
  });
  markerArray.push(marker)
  stopoverMarkers.push(marker)
}



function selectMarkerClosestToDestination(vehicle) {
  //Selecting the closest marker to destination as long as it is not out of the vehicle capacity range
  //CURRENTLY BUGGED
  let waypoints = chargingPointsMarkers;

  for (let x = waypoints.length - 1; x >= 0; x--) {
    if (calculateDistance(waypoints[x], start) > (vehicle.status * vehicle.consumption)) {
      waypoints.splice(x, 1)
    }
  }

  for (let x = waypoints.length - 1; x > 0; x--) {
    if (calculateDistance(waypoints[x], end) > (calculateDistance(waypoints[x - 1], end))) {
      waypoints.splice(x, 1);
    } else {
      waypoints.splice(x - 1, 1);
    }
  }
  return waypoints[0];
}

function calculateDistance(p1, p2) {
  //Uses the Google geometry library to calculate distance between two Markers
  let a = p1.getPosition();
  let b = p2.getPosition();

  let distance = (google.maps.geometry.spherical.computeDistanceBetween(a, b) / 1000).toFixed(2);
  return distance;
}

function showSteps(directionResult, stepDisplay, map) {
  // For each step, place a marker, and add the text to the marker's infowindow.
  // Also attach the marker to an array so we can keep track of it and remove it
  // when calculating new routes.
  //NOT CURRENTLY IMPLEMENTED/USED
  const myRoute = directionResult.routes[0].legs[0];

  for (let i = 0; i < myRoute.steps.length; i++) {
    const marker = (markerArray[i] =
      markerArray[i] || new google.maps.Marker());
    marker.setMap(map);
    marker.setPosition(myRoute.steps[i].start_location);
    attachInstructionText(
      stepDisplay,
      marker,
      myRoute.steps[i].instructions,
      map
    );
  }
}

function attachInstructionText(stepDisplay, marker, text, map) {
  google.maps.event.addListener(marker, "click", () => {
    // Open an info window when the marker is clicked on, containing the text
    // of the step.
    //NOT CURRENTLY IMPLEMENTED/USED
    stepDisplay.setContent(text);
    stepDisplay.open(map, marker);
  });
}
/* Always set the map height explicitly to define the size of the div
       * element that contains the map. */
#map {
    height: 100%;
}

/* Optional: Makes the sample page fill the window. */
html,
body {
    height: 100%;
    margin: 0;
    padding: 0;
}

#floating-panel {
    position: absolute;
    top: 10px;
    left: 25%;
    z-index: 5;
    background-color: #fff;
    padding: 5px;
    border: 1px solid #999;
    text-align: center;
    font-family: "Roboto", "sans-serif";
    line-height: 30px;
    padding-left: 10px;
}

#warnings-panel {
    width: 100%;
    height: 10%;
    text-align: center;
}
<!DOCTYPE html>
<html>
<head>
    <title>EV Trip Route Finder</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
    <script
            src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap&libraries=&v=weekly"
            defer
    ></script>
</head>
<body>
<div id="floating-panel" >
    <b>Start: </b>
    <input id="start" type="text" value="Nantes">
    <b>End: </b>
    <input id="end" type="text" value="Paris">
    <input id="submit" type="button" value="GO" />
</div>
<div id="map"></div>
&nbsp;
<div id="warnings-panel"></div>
</body>
</html>
geocodezip
  • 147,872
  • 13
  • 194
  • 222