JavaScript似乎不会等待返回值

30

我已经苦战了一段时间。 我刚开始学习JavaScript,并一直以为我所编写的代码是异步运行的。以下是一个通用示例:

我在函数a中运行一些代码。 然后函数A调用函数B,需要将一个变量返回给A,以便A可以在其后续操作中使用它。 但是当A调用B时,它仍然继续运行自己的代码,不等待其返回值,而且B并没有足够快,以至于在A到达需要使用返回值的时候,我会得到一个未定义变量类型错误

我解决这个问题的方法是让函数A调用函数B,然后调用函数C执行后续操作,这与A将使用返回值的方式不同... ... 通过调用来串行化代码,虽然有点繁琐...

以下是一个实际代码出现此问题的示例:

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

注意: 在我的html中,initialize将会被body onload="initialize()"调用。

因此,问题在于makeMap需要由Geocode函数获取的纬度和经度值,但是我在控制台中收到一个指出 results 未定义的错误。发生了什么?我来自Java,所以对JS中的数据流如何发生有些困惑!这将是今后宝贵的课程!

关于一个相关的问题: 我应该如何跨外部脚本拆分我的函数?什么是良好实践?我的所有功能是否应该挤进一个外部.js文件中,还是应该将类似的功能分组在一起?


3
好的,谢谢,我是新来的! - Georges Krinker
4个回答

39

你似乎对问题有很好的理解,但听起来你不熟悉解决它的方法。最常见的方法是使用回调函数来解决。这基本上是异步等待返回值的方式。以下是你可以在你的情况下如何使用它:

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

}

...

15

我一直以为我写的代码是在异步运行。

没错,它确实是异步运行的。你的 geocode 函数无法返回 Google API 调用的结果,因为该函数会在 Google 调用完成之前就返回。请参见下面的说明:

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

相反地,你必须编写你的geocode函数,使其接受一个回调函数,当它有结果时会调用它。例如:

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

那么,不要像这样使用它:

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

您可以按照以下方式调用它:
geocode(someArgHere, function() {
    if (results) {
        doSomething(results);
    }
    else {
        doSomethingElse();
    }
});

例如,您采用完全异步的方式。

匿名函数(接受两个参数:结果和状态),难道不是在做这件事吗? - Georges Krinker
@GeorgesKrinker:是的,如果您不需要在将结果返回给调用代码之前转换结果,则可以直接将“callback”传递给Google。 - T.J. Crowder

1

确实,您正确地意识到这些调用是异步的,并且您没有得到正确的返回值。

通常,在js中调用函数时,它们是同步的。

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

然而,在某些情况下,例如进行ajax或jsonp调用时,它是异步完成的。这正是当您调用geocode()时发生的情况。

您的执行:

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.

因此,具体来说,利用已经内置在地理编码器调用中的回调函数:

if (status == google.maps.GeocoderStatus.OK) {
    makeMap(results);
}

如果我循环遍历一个数组并返回它,因为我需要它,而不是调用外部服务(如上面的异步谷歌调用),似乎也会发生这种情况...为什么会这样? - Georges Krinker
那应该是同步的。你能在jsFiddle上重新制作一下吗? - Julian H. Lam
好的,我不确定如何使用它,但是http://jsfiddle.net/gpLYH/1/。问题发生的地方是当loadData()尝试从getStations()检索数据并应返回一个包含车站的数组后,调用addMarkers()。 - Georges Krinker

1
匿名函数内的返回语句从匿名函数中返回,而不是从外部的geocode函数中返回。geocode函数返回undefined。geocoder.geocode方法可以在任何时候调用匿名函数,同步或异步。请查看相关文档。

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