2008-11-09 13 views
15

To jest rodzaj problemu z barmanezją, ponieważ kod działa idealnie dokładnie tak, jak jest, tylko trochę irytuje mój zmysł estetyczny. Zwracam się do Stack Overflow, ponieważ mój własny mózg właśnie mnie zawodzi.Funkcje wywoływania zwrotów i rekursja JavaScript

Oto fragment kodu, który wyszukuje adres za pomocą interfejsu API Map Google Google i umieszcza znacznik na mapie. Jednak czasami początkowe wyszukiwanie kończy się niepowodzeniem, dlatego chcę powtórzyć proces z innym adresem.

geocoder.getLatLng(item.mapstring, function(point) { 
    if (!point) { 
     geocoder.getLatLng(item.backup_mapstring, function(point) { 
      if (!point) return; 
      map.setCenter(point, 13); 
      map.setZoom(7); 
      map.addOverlay(new GMarker(point)); 
     }) 
     return; 
    } 
    map.setCenter(point, 13); 
    map.setZoom(7); 
    map.addOverlay(new GMarker(point)); 
}) 

(Drugi parametr getLatLng jest funkcja zwrotna).

Oczywiście widać, że trzy linie, które centrum i powiększać mapę i dodać znacznik są duplikowane, raz w podstawowej zwrotnego i raz w "callback'u zwrotnym" (ha ha). Czy potrafisz znaleźć sposób na wyrażenie wszystkiego bez żadnej redukcji? Zdobywasz punkty bonusowe i moje uwielbienie, jeśli twoje rozwiązanie działa dla dowolnej liczby ciągów map kopii zapasowej.

Odpowiedz

21

Inne odpowiedzi są dobre, ale jest jeszcze jedna opcja. To pozwala zachować ten sam formularz rozpoczętego ale wykorzystuje trick nazywania funkcji lambda, dzięki czemu można się do niego rekurencyjnie:

mapstrings = ['mapstring1', 'mapstring2', 'mapstring3']; 

geocoder.getLatLng(mapstrings.shift(), function lambda(point) { 
    if(point) { 
     // success 
     map.setCenter(point, 13); 
     map.setZoom(7); 
     map.addOverlay(new GMarker(point)); 
    } 
    else if(mapstrings.length > 0) { 
     // Previous mapstring failed... try next mapstring 
     geocoder.getLatLng(mapstrings.shift(), lambda); 
    } 
    else { 
     // Take special action if no mapstring succeeds? 
    } 
}) 

Po raz pierwszy symbol „lambda” jest używany, to jest wprowadzić go jako nową literalną nazwę funkcji. Przy drugim użyciu jest to rekurencyjne odniesienie.

Funkcja dosłowne nazywanie działa w Chrome i zakładam, że działa w większości nowoczesnych przeglądarek, ale nie testowałem tego i nie wiem o starszych przeglądarkach.

+0

Nie potrzebujesz dosłownego nazewnictwa, możesz użyć tego, czego użyłem w moim rozwiązaniu - arguments.callee odnosi się do funkcji. –

+4

Dosłowne nazywanie jest czystsze i mniej rozmowne niż twoje rozwiązanie. –

+0

Nadawanie nazwy funkcji zamiast pozwalać jej na odwoływanie się do siebie poprzez arguments.callee jest "droga" czystsza? LOL - Myślę, że to jest trochę subiektywne, szczerze mówiąc. :) –

1

Co powiesz na to?

function place_point(mapstrings,idx) 
{ 
    if(idx>=mapstrings.length) return; 
    geocoder.getLatLng(mapstrings[idx], 
         function(point) 
         { 
          if(!point) 
          { 
           place_point(mapstrings,idx+1); 
           return; 
          } 
          map.setCenter(point, 13); 
          map.setZoom(7); 
          map.addOverlay(new GMarker(point)); 
         }); 
} 

Tyle ciągów kopii zapasowych, ile chcesz. Po prostu wywołaj go za pomocą 0 jako drugiego argumentu za pierwszym razem.

2

Tak, czynnik go do funkcji :)

geocoder.getLatLng(item.mapstring, function(point) { 
    if (!point) { 
     geocoder.getLatLng(item.backup_mapstring, function(point) { 
       if (point) { 
        setPoint(point); 
       } 
     }) 
     return; 
    } 

    function setPoint(point) { 
     map.setCenter(point, 13); 
     map.setZoom(7); 
     map.addOverlay(new GMarker(point)); 
    } 

    setPoint(point); 
}); 
8

Jest nadzwyczaj miły sposób wykonywania rekursji w konstrukcjami językowymi, które wyraźnie nie obsługują zagnieżdżenia nazywany operator paradoksalny. Najbardziej znanym jest Y-Combinator.

Here is the Y combinator for a function of one parameter in Javascript:

function Y(le, a) { 
    return function (f) { 
     return f(f); 
    }(function (f) { 
     return le(function (x) { 
      return f(f)(x); 
     }, a); 
    }); 
} 

Wygląda to trochę przerażające, ale trzeba tylko napisać, że jeden raz. Używanie go jest naprawdę proste. Zasadniczo, bierzesz oryginalną wartość lambda jednego parametru i zmieniasz ją w nową funkcję dwóch parametrów - pierwszy parametr jest teraz rzeczywistym wyrażeniem lambda, które możesz wykonać wywołaniem rekursywnym, drugi parametr jest oryginalnym pierwszym parametrem (point), którego chcesz użyć.

W ten sposób możesz go użyć w swoim przykładzie. Zauważ, że używam mapstrings jako listy ciągów do wyszukania, a funkcja pop zniszczyłaby element z głowy.

geocoder.getLatLng(pop(mapstrings), Y(
    function(getLatLongCallback, point) 
    { 
    if (!point) 
    { 
     if (length(mapstrings) > 0) 
     geocoder.getLatLng(pop(mapstrings), getLatLongCallback); 
     return; 
    } 

    map.setCenter(point, 13); 
    map.setZoom(7); 
    map.addOverlay(new GMarker(point)); 
    });