JavaScript doesn't seem to wait for return values

46,463

Solution 1

You seem to have a good understanding of the problem, but it sounds like you aren't familiar with the way to solve it. The most common way to address this is by using a callback. This is basically the async way to wait for a return value. Here's how you could use it in your case:

function initialize() {
    //Geocode Address to obtin Lat and Long coordinates for the starting point of our map
    geocoder = new google.maps.Geocoder();
    geocode(geocoder, function(results) {
        // This function gets called by the geocode function on success
        makeMap(results[0].geometry.location.lat(), results[0].geometry.location.lng());        
    });
}

function geocode(geocoder, callback) {
    //do geocoding here...

    var address = "3630 University Street, Montreal, QC, Canada";
    geocoder.geocode({ 'address': address }, function (results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
            // Call the callback function instead of returning
            callback(results);
        } else {
            alert("Geocode was not successful for the following reason: " + status);
        }
   });

}

...

Solution 2

I...have been under the impression that the code I've been writing has been running asynchronously.

Yes, it does. Your geocode function cannot return the results of the call to the Google API, because the function returns before the Google call completes. See note below:

function geocode(geocoder) {
    //do geocoding here...

    var address = "3630 University Street, Montreal, QC, Canada";
    geocoder.geocode({ 'address': address }, function (results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
           // +---------- This doesn't return anything from your
           // v           geocode function, it returns a value from the callback
           return results;
            }
         else {
            alert("Geocode was not successful for the following reason: " + status);
        }
   });
}

Instead, you must code your geocode function so that it accepts a callback which it will call when it has the results. E.g.:

// Added a callback arg ---v
function geocode(geocoder, callback) {
    //do geocoding here...

    var address = "3630 University Street, Montreal, QC, Canada";
    geocoder.geocode({ 'address': address }, function (results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
           // v---------- Call the callback
           callback(results);
            }
         else {
            alert("Geocode was not successful for the following reason: " + status);
            callback(null); // <--- Call the callback with a flag value
                            // saying there was an error
        }
   });
}

Then, instead of using it like this:

var results = geocode(someArgHere);
if (results) {
    doSomething(results);
}
else {
    doSomethingElse();
}

You call it like this:

geocode(someArgHere, function() {
    if (results) {
        doSomething(results);
    }
    else {
        doSomethingElse();
    }
});

E.g., you go fully asynchronous.

Solution 3

The return statement inside the anonymous function returns from the anonymous function, not from the outer geocode function. The geocode function returns undefined. The geocoder.geocode method may call the anonymous function whenever it wants, sync or async. Check the docs for it.

Solution 4

Indeed, you are correct in realizing that the calls are asynchronous, and you are not getting a proper return value.

Normally, when functions are called in js, they are synchronous.

e.g. a() calls b(), and a() waits until b() to finish before continuing.

However, in certain situations, such as making ajax or jsonp calls, it is done asynchronously. This is precisely what is happening when you call geocode().

Your execution:

initialize() is called;
initialize() calls geocoder();
geocoder makes a request to Google, and returns null in the meantime.
initialze() calls makemap()
the Google geocoder returns at some point, and executed the success callback, which you have defined as "return results;", but there is nothing to return, since the function has already ended.

So, specifically, utilise the callback that is already built into the geocoder call:

if (status == google.maps.GeocoderStatus.OK) {
    makeMap(results);
}
Share:
46,463
Georges Krinker
Author by

Georges Krinker

Updated on December 05, 2020

Comments

  • Georges Krinker
    Georges Krinker over 3 years

    I've been struggling with this for a while now. I'm new to Javascript, and have been under the impression that the code I've been writing has been running asynchronously. Here is a generic example:

    I run some code in function a. Function A then calls Function B, who needs to return a variable to A so A can use it in its later operations. It seems though that when A calls B, it still continues to run its own code, not waiting blocked for its return value, and B isn't fast enough such that A ends up reaching the point where it would have needed to use the return value and I get an undefined variable type error.

    The way I have worked around this is have function A call Function B which then calls a Function C that would do what the later operations that A would be doing with the return value....I'm kind of serializing my code through calls instead of returns...that is cumbersome though...

    Here is an example of when it happens in actual code:

    function initialize() {
        //Geocode Address to obtin Lat and Long coordinates for the starting point of our map
        geocoder = new google.maps.Geocoder();
        var results = geocode(geocoder);
        makeMap(results[0].geometry.location.lat(), results[0].geometry.location.lng());
    
    }
    
    function geocode(geocoder) {
        //do geocoding here...
    
        var address = "3630 University Street, Montreal, QC, Canada";
        geocoder.geocode({ 'address': address }, function (results, status) {
            if (status == google.maps.GeocoderStatus.OK) {
               return results;
                }
             else {
                alert("Geocode was not successful for the following reason: " + status);
            }
       });
    
    }
    
    function makeMap(lat, long) {
      //  alert(lat); for debuging
        var mapOptions = {
            center: new google.maps.LatLng(lat, long),
            zoom: 17,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };
         map = new google.maps.Map(document.getElementById("map_canvas"),
            mapOptions);
    }
    

    Note: initialize gets called by body onload="initialize()" in my html.

    So the issue is that the makeMap requires the lat and longitude values obtained by the Geocode function, but I get an error in the console saying results is undefined. What is going on? I came from Java so I'm a little confused about how data flow is happening here in JS! This will be valuable lessons for the future!

    On a side question: How should I split my functions across external scripts? What is considered good practice? should all my functions be crammed into one external .js file or should I group like functions together?

  • Georges Krinker
    Georges Krinker over 11 years
    This also seems to happen if instead of a call to an external service (like the async google call above), I looped through an array and returned the array because I needed it...why is that?
  • Georges Krinker
    Georges Krinker over 11 years
    isn't that what the anonymous function (that takes two parameters results, status) is doing?
  • Julian H. Lam
    Julian H. Lam over 11 years
    That should be synchronous. Can you reproduce this in a jsFiddle?
  • T.J. Crowder
    T.J. Crowder over 11 years
    @GeorgesKrinker: Yes, you could pass callback directly to Google if you don't need to transform the result before returning it to the calling code.
  • Georges Krinker
    Georges Krinker over 11 years
    Ok I'm not sure how to use that but jsfiddle.net/gpLYH/1 The place where the problem happens is when loadData() calls addMarkers() after trying to retreave the data from getStations() that should have returned an array with the stations...