2012-11-19 32 views
25

Od pewnego czasu walczę z tym. Jestem nowicjuszem w JavaScript i miałem wrażenie, że napisany przeze mnie kod działa asynchronicznie. Oto ogólny przykład:JavaScript nie wydaje się czekać na wartości zwracane

Uruchomiłem kod w funkcji a. Funkcja A następnie wywołuje funkcję B, która musi zwrócić zmienną do A, aby A mógł jej użyć w późniejszych operacjach. Wydaje się jednak, że gdy A dzwoni B, nadal kontynuuje swój własny kod, nie czekając zablokowany na swoją wartość zwracaną, a B nie jest wystarczająco szybki, aby A skończył osiągając punkt, w którym musiałby użyć zwrotu wartość i otrzymuję błąd niezdefiniowanego typu zmiennej .

Sposób, w jaki pracowałem nad tym, to funkcja A, która wywołuje funkcję B, która następnie wywołuje funkcję C, która wykona to, co będą później wykonywać operacje A z wartością zwracaną .... Jestem serializacją mój kod poprzez zaproszenia zamiast zwrotów ... to jest kłopotliwe, choć ...

Oto przykład, gdy zdarza się w rzeczywistym kodzie:

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

Uwaga: initialize jest wywoływana przez onload ciała = "initialize()" w moim html.

Problem polega na tym, że makeMap wymaga wartości długości i długości geograficznej uzyskanej przez funkcję Geocode, ale pojawia się błąd w konsoli, informujący, że wyniki są niezdefiniowane. Co się dzieje? Pochodzę z Javy, więc jestem trochę zdezorientowany tym, jak przepływ danych dzieje się tutaj w JS! To będzie cenna lekcja na przyszłość!

Na pytanie boczne: Jak podzielić funkcje na skrypty zewnętrzne? Co uważa się za dobrą praktykę? czy wszystkie moje funkcje powinny zostać wepchnięte do jednego zewnętrznego pliku .js, czy powinienem pogrupować podobne funkcje?

+3

Dobra, dziękuję, jestem nowy tutaj! –

Odpowiedz

31

to wydaje się mieć dobre zrozumienie problemu, ale brzmi, jakbyś nie znał sposobu rozwiązania tego problemu. Najczęstszym sposobem rozwiązania tego problemu jest użycie wywołania zwrotnego. Jest to w zasadzie asynchroniczny sposób oczekiwania na wartość zwracaną. Oto, jak możesz go użyć w swoim przypadku:

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

} 

... 
11

Ja ... miałem wrażenie, że kod, który piszę, działa asynchronicznie.

Tak, robi. Twoja funkcja geocode nie może zwrócić wyników połączenia do interfejsu API Google, ponieważ funkcja zwraca się przed zakończeniem połączenia Google. Patrz uwaga poniżej:

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

Zamiast tego należy kodować swoją funkcję geocode więc, że akceptuje wywołania zwrotnego, która będzie zadzwonić, gdy ma wyniki. Np .:

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

Następnie, zamiast używać go tak:

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

to nazwać tak:

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

przykład idziesz pełni asynchronicznym.

+0

nie jest tym, co robi anonimowa funkcja (która przyjmuje dwa parametry, status)? –

+0

@GeorgesKrinker: Tak, możesz przekazać 'callback' bezpośrednio do Google, jeśli nie musisz przekształcać wyniku przed zwróceniem go do kodu wywołującego. –

1

Instrukcja return wewnątrz anonimowej funkcji zwraca z funkcji anonimowej, a nie z zewnętrznej funkcji geokodowania. Funkcja geocode zwraca wartość undefined. Metoda geocoder.geocode może wywoływać funkcję anonimową w dowolnym momencie, synchronizacji lub asynchronizacji. Sprawdź dokumenty na ten temat.

1

Rzeczywiście, masz rację, zdając sobie sprawę, że połączenia są asynchroniczne i nie uzyskujesz prawidłowej wartości zwracanej.

Zwykle, gdy funkcje są wywoływane w js, są one synchroniczne.

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

Jednak w niektórych sytuacjach, takich jak wywoływanie wywołań ajax lub jsonp, jest to wykonywane asynchronicznie. Dokładnie to się dzieje, gdy zadzwonisz pod numer geocode().

Twoje wykonanie:

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. 

Więc konkretnie wykorzystać zwrotnego, który jest już wbudowany w zaproszeniu geocoder:

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

To zdaje się również zdarzać, jeśli zamiast wywoływać usługę zewnętrzną (jak asynchroniczne wywołanie google powyżej), przełączyłem się przez tablicę i zwróciłem tablicę, ponieważ jej potrzebowałem ... dlaczego tak jest? –

+0

To powinno być synchroniczne. Czy możesz odtworzyć to w [jsFiddle] (http://jsfiddle.net)? –

+0

OK Nie jestem pewien, jak tego użyć, ale http://jsfiddle.net/gpLYH/1/ Miejsce, w którym pojawia się problem, to gdy loadData() wywoła metodę addMarkers() po próbie odzyskania danych z getStations() powinieneś zwrócić tablicę ze stacjami ... –

Powiązane problemy