Understanding Callback Functions in Google Maps API: How Asynchronous Calls can Impact JavaScript

Seeking to enhance some code, I'm exploring the utilization of Google Maps for longitude and latitude calculations. Initially encountering an issue with the asynchronous nature of Google maps data retrieval, a kind individual proposed a solution. Despite my efforts over several days, it appears that my limited experience in JavaScript is impeding progress. Here is the complete code snippet along with my original query: Asynchronous Google Maps request throwing off Javascript. Although I suspect it's a minor issue, I've attempted various approaches without success. Any guidance would be greatly appreciated as I believe the solution lies within reach, yet remains elusive.

<script type="text/javascript">
   //variable declarations
  var map;
  var directionsService;
  var directionsDisplay1, directionsDisplay2, directionsDisplay3;
  var markersArray = [];
  var stations;

  var autocomplete_options = {componentRestrictions: {country: 'us'}};
  var places = new Array();
  places[0] = "Barlays Center, Brooklyn NY";
  places[1] = "Union Square, nyc";

//formerly var autocomplete_start = new google.maps.places.Autocomplete(document.getElementById("id_start"), autocomplete_options);
    var autocomplete_start = places[0];
//var autocomplete_start = new google.maps.places.PlacesService(places[0]);
    var autocomplete_end = places[1];

  //Determine distance between locations for Citibike station proximity
function getDistance(lat1,lng1,lat2,lng2) {
  var i = lat1 - lat2;
  var j = lng1 - lng2;
  return i*i + j*j;
}

function findNearestStation(lat,lng) {
  var min_distance = 99999;
  var closest_station_id;
  $.each(stations.stationBeanList, function(i, station) {
    var distance = getDistance(lat,lng, station.latitude, station.longitude);

    if (distance < min_distance) {
      min_distance = distance;
      closest_station_id = i;
    }
  });

  console.log('Closest station idx: ' + closest_station_id);

  return stations.stationBeanList[closest_station_id];
}
 //Plotting on the map
function drawMarker(lat, lng, map, title, marker_text) {
  var point = new google.maps.LatLng(lat, lng);

  var marker = new google.maps.Marker({
    position : point,
    map : map,
    title : title,
    icon: 'http://chart.apis.google.com/chartchst=d_map_pin_letter&chld='+marker_text+'|FE6256|000000'
  });

  markersArray.push(marker);
}


function clearMarkers() {
  for (var i = 0; i < markersArray.length; i++ ) {
    markersArray[i].setMap(null);
  }
  markersArray = [];
}

function drawMap() {
  var center = new google.maps.LatLng(40.704066,-73.992727);
  var mapOptions = {
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    mapTypeControlOptions: { style: google.maps.MapTypeControlStyle.DROPDOWN_MENU },
    zoom: 14,
    center: center
  };

  map = new google.maps.Map(document.getElementById("map"), mapOptions);
  directionsService = new google.maps.DirectionsService();
  directionsDisplay1 = new google.maps.DirectionsRenderer();
  directionsDisplay1.setMap(map);
  directionsDisplay1.setPanel(document.getElementById("directions-panel1"));
  directionsDisplay2 = new google.maps.DirectionsRenderer();
  directionsDisplay2.setMap(map);
  directionsDisplay2.setPanel(document.getElementById("directions-panel2"));
  directionsDisplay3 = new google.maps.DirectionsRenderer();
  directionsDisplay3.setMap(map);
  directionsDisplay3.setPanel(document.getElementById("directions-panel3"));

  $.getJSON('./static/js/stations.json', function(data) {
    stations = data;
    var bounds = new google.maps.LatLngBounds();
    $.each(data.stationBeanList, function(i, station) {
      if (station.statusValue == 'In Service') {
        var point = new google.maps.LatLng(station.latitude, station.longitude);
        bounds.extend(point);
      }
    });
    map.setCenter(bounds.getCenter(), map.fitBounds(bounds));
    autocomplete_start.bindTo('bounds', map);
    autocomplete_end.bindTo('bounds', map);
  });
}

$(document).ready(function(){
  drawMap();
});

$('#btn-reset').click(function(){
  $('#directions-panel1').empty();
  $('#directions-panel2').empty();
  $('#directions-panel3').empty();
  $('#directions-info1').empty();
  $('#directions-info2').empty();
  $('#directions-info3').empty();
  drawMap();
});

$('#btn-get-directions').click(function(){
  //retrieve directions on the map

  var start = autocomplete_start;
  var end   = autocomplete_end;
  var start_lat=getLat(start);
  //legacy code - originally had Google autocomplete
  var start_lng;
  var end_lat;
  var end_lng;
 // getLat(start);


  function getLat(loc) {
    var geocoder = new google.maps.Geocoder();
    geocoder.geocode({'address': loc}, function postcodesearch(results, status) 
    {   
      if (status == google.maps.GeocoderStatus.OK) 
    {
      var lat = results[0].geometry.location.lat();
      //alert(lat);
      return lat;

      //alert(start_lng);
    }
  else {
    alert("Error");
  }
  });
 }
//Original functions
function getLng(loc) {
var geocoder_lng = new google.maps.Geocoder();
  geocoder_lng.geocode({'address': loc}, function postcodesearch(results, status) 
{   
  if (status == google.maps.GeocoderStatus.OK) 
{
  var lng = results[0].geometry.location.lng();
 // alert(lng);
  return lng;
  //alert(end_lat);

  //doRest();
}
  else {
     alert("Error");
   }
});
}

//This function was authored by someone else
 function getLatLng(loc, callback) {

    var geocoder = new google.maps.Geocoder();
    geocoder.geocode({'address': loc}, function postcodesearch(results, status) 
  {  
    if (status == google.maps.GeocoderStatus.OK) 
    {
      var lat = results[0].geometry.location.lat();
      var lng = results[0].geometry.location.lng();
      alert("234234");
      return(lat, lng);
    }
   }
  );}

getLatLng(start);
doRest()
 //Remaining code
  function doRest(){

  //var start_lat = start.geometry.location.lat();
  //alert("DO REST");

  var start_lng = getLng(start);
  alert(start_lng);
  var start_p = new google.maps.LatLng(getLatLng(start));
  //alert(start_p);
  //alert(start_p);
  //var end_lat = end.geometry.location.lat();
  //var end_lng = end.geometry.location.lng();
  var end_p = new google.maps.LatLng(getLatLng(end));



  var start_station = findNearestStation(start_p);
  var end_station = findNearestStation(end_p);

  alert("asdf");
  var start_station_p = new google.maps.LatLng(start_station.latitude, start_station.longitude);
  alert("wtf");
  var end_station_p = new google.maps.LatLng(end_station.latitude, end_station.longitude);
  // alert("stations");

  var dir_bounds = new google.maps.LatLngBounds();
  dir_bounds.extend(start_p);
  dir_bounds.extend(end_p);
  dir_bounds.extend(start_station_p);
  dir_bounds.extend(end_station_p);

  drawMarker(start_lat, start_lng, map, $('#id_start').val(), '1');
  drawMarker(start_station.latitude, start_station.longitude, map, start_station.stationName, '2');
  drawMarker(end_station.latitude, end_station.longitude, map, end_station.stationName, '3');
  drawMarker(end_lat, end_lng, map, $('#id_end').val(), '4');
}

  //Three calls will be made, 1) walking, 2) biking, 3) walking
  // Walking from start to station 1
  var request1 = {
    origin:start_p,
    destination:start_station_p,
    travelMode: google.maps.TravelMode.WALKING
  };
  directionsService.route(request1, function(result, status) {
    if (status == google.maps.DirectionsStatus.OK) {
      $('#directions-info1').html('Walk from ' + $('#id_start').val() + ' to station at ' + start_station.stationName);
      directionsDisplay1.setDirections(result);
    }
  });

  // Biking from station 1 to station 2
  var request2 = {
    origin:start_station_p,
    destination:end_station_p,
    travelMode: google.maps.TravelMode.BICYCLING
  };
  directionsService.route(request2, function(result, status) {
    if (status == google.maps.DirectionsStatus.OK) {
      $('#directions-info2').html('Bike from station at ' + start_station.stationName + ' to station at ' + end_station.stationName);
      directionsDisplay2.setDirections(result);
    }
  });

  // Walking from station 2 to end
  var request3 = {
    origin:end_station_p,
    destination:end_p,
    travelMode: google.maps.TravelMode.WALKING
  };
  directionsService.route(request3, function(result, status) {
    if (status == google.maps.DirectionsStatus.OK) {
      $('#directions-info3').html('Walk from station at ' + end_station.stationName + ' to ' + $('#id_end').val());
      directionsDisplay3.setDirections(result);
    }
  });

  google.maps.event.addListener(directionsDisplay1, 'directions_changed', function() {
    map.setCenter(dir_bounds.getCenter(), map.fitBounds(dir_bounds));
  });
  google.maps.event.addListener(directionsDisplay2, 'directions_changed', function() {
    map.setCenter(dir_bounds.getCenter(), map.fitBounds(dir_bounds));
  });
  google.maps.event.addListener(directionsDisplay3, 'directions_changed', function() {
    map.setCenter(dir_bounds.getCenter(), map.fitBounds(dir_bounds));
  });
});

Answer №1

One issue that stands out is the attempt to return values from the getlatLng function, which won't work due to the asynchronous nature of the geocode call. The solution lies in using callbacks. Instead of the previous approach:

function getLatLng() {
    return somevalue;
}
latlng = getLatLng();
// carry out some actions

You should opt for a callback-based method like this:

function getLatLng(callback) {
    callback(somevalue);
}
getLatLng(function(latlng) {
    // carry out some actions
});

Below is an example of the getLatLng implementation that includes a start, end, and callback. The callback function takes four parameters: start_lat, start_lng, end_lat, end_lng. Although syntax checks are not performed, this code snippet serves as a guide.

function getLatLng(start, end, callback) {
    var geocoder = new google.maps.Geocoder();

    // geocode the start location
    geocoder.geocode({'address': start}, function(results, status) {  
        if (status == google.maps.GeocoderStatus.OK) {
            var startlat = results[0].geometry.location.lat();
            var startlng = results[0].geometry.location.lng();
            console.log('Start location: ' + startlat + ', ' + startlng);

            // geocode the end location
            geocoder.geocode({'address': end}, function(results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    var endlat = results[0].geometry.location.lat();
                    var endlng = results[0].geometry.location.lng();
                    console.log('End location: ' + endlat + ', ' + endlng);

                    // invoke the callback function
                    callback(startlat, startlng, endlat, endlng);
                }
            });
       }
    });
}

// The third parameter passed here to getLatLng is the callback function:
getLatLng(start, end, function(start_lat, start_lng, end_lat, end_lng) {
    // Additional code can be added here
    var start_p = new google.maps.LatLng(start_lat, start_lng);
    var end_p = new google.maps.LatLng(end_lat, end_lng);
    var start_station = findNearestStation(start_p);
    var end_station = findNearestStation(end_p);
    // etc
});

Note: Use your browser's debugging tools (Shift-Ctl-J in Chrome) to:

  1. Check for Javascript errors in the console
  2. Include "console.log" statements in your code to track its progress
  3. Add breakpoints to the code for tracing its execution

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Permission of Files on the Apache Server

While trying to make an AJAX put request to my server using Apache Tomcat 6.0, I keep receiving a 403 Forbidden error. Does anyone have any insights or solutions for this issue? ...

Remove files from the server using an AJAX request

I am attempting to delete files on the SERVER using JavaScript, and I have already consulted the advice provided in this JavaScript file deletion thread My current JavaScript code looks like this: deleteFile = function() { return $.ajax({ url: "de ...

The THREE.EffectComposer function cannot be used as a constructor

Greetings fellow developers! Today, I embarked on a journey to create a volumetric light scattering shader using THREE.js. My inspiration came from this amazing example: https://codepen.io/abberg/pen/pbWkjg However, as I attempted to implement the code, ...

Flow object with Angular using ng-flow: Existing flow object

Just a quick question that I can't seem to find an answer for on my own or through searching online. Currently, I have a button set up for clicking and uploading files. However, I also want to incorporate drag and drop functionality using the same fra ...

Cover the <img ...> tag in HTML using JavaScript

I'm currently working on creating a simple game involving sliding ice-blocks. I ran into an issue while testing this JSFiddle and I'm looking to "hide" the image/button triggered by the line alert('Game starts!');. I attempted using sta ...

Tips on accessing the JS file api within an angular component.ts file

I've got a function in my JS file located at src/server/js/controller.js, and I'm trying to use that API within a component's ts file. I attempted the following code to achieve this but it doesn't seem to be working properly. controlle ...

The like button animation works perfectly for individual posts, but for multiple posts, only the like button on the first post

Having an issue with the like button code on a single news feed. The button works for the first post, but when clicking on other posts, it only affects the first post button. I am using post UI and looping the UI based on the total post count without Javas ...

Using JQuery to search for and remove the id number that has been clicked from a specific value

I am in need of assistance with deleting the clicked id number from an input value using jQuery. I am unsure of how to proceed with this task and would appreciate some guidance. To simplify my request, let me explain what I am trying to accomplish. Take ...

Upon completing the update to the most recent version of aurelia, I encountered an issue where the project failed to run and displayed the error message: "unable to locate module './aurelia-framework'" in webpack

My current project is based on the aurelia webpack/es2016 navigation skeleton from a couple of months back. Up until today, everything was running smoothly. However, after deleting my node_modules directory and performing a fresh npm install, the front en ...

Tips for shrinking a sticky header when scrolling using Angular and JavaScript

Looking for a way to modify the header component in an Angular application so that it shrinks as the user scrolls down while maintaining its sticky functionality. How can I adjust the display of the lower half of the header to show no text when the user s ...

Utilizing ng-disabled with a custom directive

Is it possible to achieve the following: <directiveName parameter1=value1 parameter2=value2 ng-disabled="true"> </directiveName> I tried this but couldn't get it to work and didn't find many examples of its use. However, I can togg ...

Design a progress bar that advances in increments of at least two and up to a maximum of

My task involves managing a simple list. const [progressBar, setProgressBar] = useState([ { isActive: false, name: "step1" }, { isActive: false, name: "step2" }, { isActive: false, name: "step3" }, { isActive ...

What could be causing errors in converting Vue.js code into a single HTML file?

I discovered a Vue.js sample project on this website and now I want to execute this code in a single HTML file using Vue.js. I attempted: <!DOCTYPE html> <html> <head> <title>My first Vue app</tite> <script type="te ...

Achieving success despite facing rejection

I am currently utilizing the bluebird settle method in order to verify results for promises without concerning myself with any rejections. Interestingly, even though I have rejected the promise within the secondMethod, the isFulfilled() still returns tru ...

Error: The class constructor [] must be called with the 'new' keyword when creating instances in a Vite project

I encountered an issue in my small Vue 3 project set up with Vite where I received the error message Class constructor XX cannot be invoked without 'new'. After researching online, it seems that this problem typically arises when a transpiled cla ...

Learn the process of zipping a folder in a Node.js application and initiating the download of the zip file afterwards

After encountering issues with the latest version of the npm package adm-zip 0.4.7, I reverted to an older version, adm-zip 0.4.4. However, despite working on Windows, this version does not function correctly on Mac and Linux operating systems. Additionall ...

The functionality of .setErrors is not applicable to FormControls within the Angular framework

When using functions like .setError with controls, I am encountering the same error message indicating that these are not recognized as functions. Below is a snippet of my code: export class AppComponent implements OnInit { // Code goes here } Within m ...

Substitute a particular element within a text

I'm currently working on an Angular 9 application where I'm implementing search filters for the Shopify Graphql API. As part of this process, I am constructing a query which looks like: first: 1, query: "tag:'featured-1'", af ...

What could be causing my object to not be added properly to a select element using JavaScript programmatically?

When it comes to customizing your pizza, the options are endless. From selecting the type of crust to choosing your favorite toppings, every detail matters. One common issue that arises is when changing the crust type affects the available sizes ...

Nuxt - Sending Relative Path as Prop Leads to Error 404

I am currently working with an array of JSON objects that I need to import onto a webpage. The process involves iterating through the data and passing the objects as a prop to a component. One of the attributes within the JSON data is a relative path for a ...