web-dev-qa-db-ja.com

JavaScriptは戻り値を待機していないようです

私はこれでしばらく苦労してきました。私はJavascriptを初めて使用しますが、これまで書いてきたコードは非同期に実行されているという印象を受けていました。一般的な例を次に示します。

関数aでコードを実行します。次に、関数Aは関数Bを呼び出します。関数Bは変数をAに返す必要があるため、Aは後の操作で変数を使用できます。しかし、AがBを呼び出すと、戻り値をブロックするのを待たずに独自のコードを実行し続け、Bが十分に速くないため、Aが戻り値を使用する必要があるポイントに到達するように見える値とndefined variable type errorを受け取ります。

これを回避する方法は、関数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);
}

注: initializeは、私のHTMLのbody onload = "initialize()"によって呼び出されます。

そのため、問題はmakeMapがGeocode関数で取得した緯度と経度の値を必要とすることですが、結果が未定義であるというエラーがコンソールに表示されます。何が起こっている?私はJavaから来たので、JSでデータフローがどのように起こっているか少し混乱しています!これは将来のための貴重な教訓になるでしょう!

副次的な質問:関数を外部スクリプトに分割するにはどうすればよいですか?グッドプラクティスと見なされるものは何ですか?すべての関数を1つの外部.jsファイルに詰め込む必要がありますか、または類似の関数をグループ化する必要がありますか?

26
Georges Krinker

あなたは問題をよく理解しているようですが、それを解決する方法に慣れていないようです。これに対処する最も一般的な方法は、コールバックを使用することです。これは基本的に、戻り値を待つ非同期的な方法です。あなたのケースでそれを使用する方法は次のとおりです。

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

}

...
35
jncraton

私は...私が書いてきたコードは非同期に実行されているという印象を受けていました。

はい、そうです。 geocode関数cannotは、Googleの呼び出しが完了する前に関数が戻るため、Google APIの呼び出しの結果を返します。以下の注を参照してください。

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

例えば、あなたは完全非同期に行きます。

12
T.J. Crowder

無名関数内のreturnステートメントは、外部ジオコード関数からではなく、匿名関数から戻ります。ジオコード関数は未定義を返します。 geocoder.geocodeメソッドは、必要に応じて、同期または非同期で匿名関数を呼び出すことができます。ドキュメントを確認してください。

1
Douglas

確かに、呼び出しが非同期であり、適切な戻り値を取得していないことを認識することは正しいです。

通常、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);
}
_
1
Julian H. Lam