2008-11-09 7 views
15

Dies ist eine Art Denkaufgabe, da der Code perfekt funktioniert, wie es ist, irritiert es nur meinen ästhetischen Sinn so leicht. Ich wende mich Stack Overflow zu, weil mein eigenes Gehirn gerade versagt.Javascript Callback-Funktionen und Rekursion

Hier ist ein Codefragment, das mithilfe der Google Maps JS-API nach einer Adresse sucht und eine Markierung auf einer Karte platziert. Manchmal schlägt die anfängliche Suche fehl, daher möchte ich den Vorgang mit einer anderen Adresse wiederholen.

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

(Der zweite Parameter zu getLatLng ist eine Callback-Funktion.)

Natürlich können Sie, dass die drei Linien sehen können, dass die Karte zentriert und zoomen, und fügen Sie den Marker dupliziert werden, einmal in dem primären Rückruf und einmal im Fallback Callback (ha ha). Können Sie eine Möglichkeit finden, das Ganze ohne Redundanz auszudrücken? Sie erhalten Bonuspunkte und meine Bewunderung, wenn Ihre Lösung für eine beliebige Anzahl von Backup-Map-Strings funktioniert.

Antwort

21

Die anderen Antworten sind gut, aber hier ist eine weitere Option. Auf diese Weise können Sie die gleiche Form halten, die Sie begonnen, aber nutzt den Trick des Lambda-Funktion zu benennen, so dass Sie sich darauf beziehen können rekursiv:

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

Das erste Mal, das Symbol „Lambda“ verwendet wird, ist es zu stellen Sie es als neuen Funktionsnamen vor. Beim zweiten Mal ist es eine rekursive Referenz.

Funktion literal Namen funktioniert in Chrome, und ich nehme an, es funktioniert in den meisten modernen Browsern, aber ich habe es nicht getestet und ich weiß nicht über ältere Browser.

+0

Sie brauchen keine literale Benennung, Sie können verwenden, was ich in meiner Lösung verwendet - arguments.callee bezieht sich auf die Funktion. –

+4

Literal Namen ist Weg sauberer und weniger gesprächig als Ihre Lösung obwohl –

+0

Benennung der Funktion, anstatt es über Argumenten auf sich selbst verweisen zu lassen. Callee ist "Weg" sauberer? LOL - Ich denke, das ist etwas subjektiv, um ehrlich zu sein. :) –

1

Wie wäre es damit?

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

Wie viele Backup-Strings, wie Sie wollen. Rufen Sie es beim ersten Mal einfach mit einer 0 als zweitem Argument an.

2

Ja, es Faktor in eine Funktion aus :)

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

Es ist eine außerordentlich schöne Methode zur Rekursion in Sprachkonstrukte durchführen, die nicht explizit Rekursion unterstützen genannt Fixpunkt combinator. Am bekanntesten ist die 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); 
    }); 
} 

Das sieht ein wenig beängstigend, aber Sie haben nur das einmal zu schreiben. Es ist eigentlich ziemlich einfach. Im Grunde nehmen Sie Ihr ursprüngliches Lambda eines Parameters, und Sie verwandeln es in eine neue Funktion von zwei Parametern - der erste Parameter ist jetzt der tatsächliche Lambda-Ausdruck, mit dem Sie den rekursiven Aufruf ausführen können, der zweite Parameter ist der ursprüngliche erste Parameter (point), die Sie verwenden möchten.

So können Sie es in Ihrem Beispiel verwenden. Beachten Sie, dass ich mapstrings als Liste der zu suchenden Strings verwende und die Pop-Funktion würde ein Element destruktiv vom Kopf entfernen.

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