2008-11-09 5 views
15

C'est une question de casse-tête, puisque le code fonctionne parfaitement bien tel quel, il irrite mon sens esthétique. Je me tourne vers Stack Overflow parce que mon propre cerveau me manque en ce moment.Fonctions de rappel Javascript et récursivité

Voici un extrait de code qui recherche une adresse à l'aide de l'API JS de Google Maps et place un marqueur sur une carte. Cependant, parfois la recherche initiale échoue, donc je veux répéter le processus avec une adresse différente.

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

(Le second paramètre à getLatLng est une fonction de rappel.)

Bien sûr, vous pouvez voir que les trois lignes qui centre et zoom sur la carte et ajouter le marqueur sont dupliqués, une fois dans le rappel primaire et une fois dans le "rappel de secours" (ha ha). Pouvez-vous trouver un moyen d'exprimer le tout sans aucune redondance? Vous gagnez des points bonus, et mon adulation, si votre solution fonctionne pour un nombre arbitraire de chaînes de carte de sauvegarde.

Répondre

21

Les autres réponses sont bonnes, mais voici une autre option. Cela vous permet de garder la même forme que vous avez commencé avec, mais utilise l'astuce de nommer votre fonction lambda de sorte que vous pouvez consulter récursivement:

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

La première fois est utilisé le symbole « lambda », il est à l'introduire comme un nouveau nom littéral de fonction. La deuxième fois qu'il est utilisé, c'est une référence récursive. La fonction de nommage littéral fonctionne dans Chrome, et je suppose que cela fonctionne dans la plupart des navigateurs modernes, mais je ne l'ai pas testé et je ne connais pas les anciens navigateurs.

+0

Vous n'avez pas besoin de noms littéraux, vous pouvez utiliser ce que j'ai utilisé dans ma solution - arguments.callee fait référence à la fonction. –

+4

La dénomination littérale est bien plus propre et moins bavarde que votre solution. –

+0

Nommer la fonction au lieu de lui permettre de se référer à elle-même via arguments.callee est "bien" plus propre? LOL - Je pense que c'est un peu subjectif, pour être honnête. :) –

1

Que pensez-vous de cela?

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

Autant de chaînes de sauvegarde que vous le souhaitez. Appelez-le simplement avec un 0 comme deuxième argument la première fois.

2

Eh oui, le facteur dehors dans une fonction :)

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

Il y a une méthode extrêmement agréable pour effectuer récursion dans des constructions de langage qui ne supportent pas explicitement récursion appelé un combinateur de point fixe . Le plus connu est le 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); 
    }); 
} 

Cela ressemble un peu effrayant, mais il vous suffit d'écrire qu'une fois. L'utiliser est en fait assez simple. Fondamentalement, vous prenez votre lambda d'origine d'un paramètre, et vous le transformez en une nouvelle fonction de deux paramètres - le premier paramètre est maintenant l'expression lambda réelle que vous pouvez faire l'appel récursif, le deuxième paramètre est le premier paramètre original (point) que vous souhaitez utiliser.

Voici comment vous pourriez l'utiliser dans votre exemple. Notez que j'utilise mapstrings comme une liste de chaînes à rechercher et la fonction pop supprimerait de manière destructive un élément de la tête.

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