Функции обратного вызова Javascript и рекурсия - PullRequest
15 голосов
/ 09 ноября 2008

Это своего рода вопрос-головоломка, так как код работает отлично, как есть, он просто немного раздражает мое эстетическое чувство. Я обращаюсь к переполнению стека, потому что мой собственный мозг сейчас меня подводит.

Вот фрагмент кода, который ищет адрес с помощью API Карт Google JS и размещает маркер на карте. Однако иногда первоначальный поиск завершается неудачно, поэтому я хочу повторить процесс с другим адресом.

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

(Второй параметр getLatLng является функцией обратного вызова.)

Конечно, вы можете видеть, что три линии, которые центрируют и увеличивают карту и добавляют маркер, дублируются, один раз в основном обратном вызове и один раз в "обратном вызове" (ха-ха). Можете ли вы найти способ выразить все это без какой-либо избыточности? Вы зарабатываете бонусные баллы и мою благодарность, если ваше решение работает с произвольным числом строк резервной карты.

Ответы [ 4 ]

21 голосов
/ 09 ноября 2008

Остальные ответы хороши, но вот еще один вариант. Это позволяет вам сохранить ту же форму, с которой вы начали, но использует трюк именования вашей лямбда-функции, чтобы вы могли ссылаться на нее рекурсивно:

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

При первом использовании символа «лямбда» он вводится как буквальное имя новой функции. Второй раз, когда он используется, это рекурсивная ссылка.

Функция буквального именования работает в Chrome, и я предполагаю, что она работает в большинстве современных браузеров, но я не проверял ее и не знаю о старых браузерах.

8 голосов
/ 09 ноября 2008

Существует чрезвычайно хороший метод для выполнения рекурсии в языковых конструкциях, которые явно не поддерживают рекурсию, называемый комбинатор с фиксированной точкой . Наиболее известным является Y-Combinator .

Вот Y-комбинатор для функции одного параметра в Javascript :

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

Это выглядит немного страшно, но вы должны написать это только один раз. Использовать его на самом деле довольно просто. По сути, вы берете исходную лямбда-выражение одного параметра и превращаете его в новую функцию из двух параметров - первый параметр теперь является фактическим лямбда-выражением, для которого вы можете выполнить рекурсивный вызов, второй параметр - это оригинальный первый параметр ( point), который вы хотите использовать.

Вот как вы можете использовать это в своем примере. Обратите внимание, что я использую mapstrings в качестве списка строк для поиска, а функция pop уничтожает элемент из головы.

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));
  });
2 голосов
/ 09 ноября 2008

Да, выведите его в функцию:)

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);
});
1 голос
/ 09 ноября 2008

Как насчет этого?

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

Столько резервных строк, сколько вы хотите. Просто вызовите его с 0 в качестве второго аргумента в первый раз.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...