2013-11-15 6 views
7

Я пытаюсь получить долготу и широту из списка адресов с API Google через скрипт Node.js. Сам вызов работает нормально, но поскольку у меня есть около 100 адресов для отправки. Я использую async.forEach в массиве, но вызовы выполняются слишком быстро, и я получаю сообщение об ошибке «Вы превысили лимит скорости для этого API».Выполнение forEach как водопад в async

Я обнаружил, что количество вызовов ограничено 2500 каждые 24 часа и максимум 10 секунд. Хотя я в порядке для 2500 в день, я делаю свои звонки слишком быстро для ограничения скорости.

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

async.forEach(final_json, function(item, callback) { 
    var path = '/maps/api/geocode/json?address='+encodeURIComponent(item.main_address)+'&sensor=false'; 
    console.log(path); 
    var options = { 
     host: 'maps.googleapis.com', 
     port: 80, 
     path: path, 
     method: 'GET', 
     headers: { 
     'Content-Type': 'application/json' 
     } 
    } 
    // a function I have who makes the http GET 
    rest.getJSON(options, function(statusCode, res) { 
     console.log(res); 
     callback(); 
    }); 
}, function() { 
    // do something once all the calls have been made 
}); 

Как вы бы хотели, чтобы достигнуть этого? Я попытался положить мой rest.getJSON внутри 100мс setTimeout но forEach перебирает все строки так быстро, что он начинает все setTimeout почти в то же время, и поэтому он ничего не меняет ...

The async.waterfall выглядит она сделал бы трюк, но дело в том, что я точно не знаю, сколько строк у меня будет, поэтому я не могу перекодировать все вызовы функций. И если честно, это сделало бы мой код действительно уродливые

+3

Вы пробовали рекурсию? Используйте обратный вызов для установки тайм-аута для рекурсивного вызова функции выборки после 100 мс. –

+1

Это похоже на достаточно общую проблему, которая подходит для общего решения. Ограничение скорости - это обычная конструкция, и встроенные скрипты setTimeout, вероятно, не подходят. Водопад может быть медленнее или быстрее, чем необходимо, поскольку все, что он делает, - это ожидание возврата последнего вызова. Если вызов занимает менее 1/10 секунды, вы все еще слишком быстр. –

+0

Почему не так просто, как 'parallel' с 10 поставленными в очередь, когда они завершены, убедитесь, что прошло всего 10 секунд, прежде чем запускать следующую партию? – WiredPrairie

ответ

3

Идея заключается в том, что вы можете создать rateLimited функцию, которая действует так же, как функция throttled или debounced, за исключением каких-либо вызовов, которые не выполняются немедленно ставятся в очередь и выполняются в по мере истечения срока действия лимита.

В принципе, он создает параллельные интервалы в 1 секунду, которые управляются посредством перенастройки таймера, но допускаются только до perSecondLimit интервалов.

function rateLimit(perSecondLimit, fn) { 
    var callsInLastSecond = 0; 
    var queue = []; 
    return function limited() { 
     if(callsInLastSecond >= perSecondLimit) { 
      queue.push([this,arguments]); 
      return; 
     } 

     callsInLastSecond++; 
     setTimeout(function() { 
      callsInLastSecond--; 
      var parms; 
      if(parms = queue.shift()) { 
       limited.apply(parms[0], parms[1]); 
      } 
     }, 1010); 

     fn.apply(this, arguments); 
    }; 
} 

Использование:

function thisFunctionWillBeCalledTooFast() {} 
var limitedVersion = rateLimit(10, thisFunctionWillBeCalledTooFast); 

// 10 calls will be launched immediately, then as the timer expires 
// for each of those calls a new call will be launched in it's place. 
for(var i = 0; i < 100; i++) { 
    limitedVersion(); 
} 
+0

Это прекрасная универсальная функция! Я воспринял это как есть, и это сработало как шарм! Ну, не совсем в первый раз, но вместо 10 я задушил его до 5 в секунду, и теперь он работает (с 10 я все равно получаю некоторые ошибки, меньше с 9, но безупречный с 5). Спасибо! –

+0

С удовольствием :) –

1

Вот как я бы взломать его (Примечание: arr Ваш массив мест):

function populate(arr, callback, pos) { 
    if(typeof pos == "undefined") 
     pos=0; 
    var path = '/maps/api/geocode/json?address='+encodeURIComponent(arr[pos].main_address)+'&sensor=false'; 
    console.log(path); 
    var options = { 
     host: 'maps.googleapis.com', 
     port: 80, 
     path: path, 
     method: 'GET', 
     headers: { 
     'Content-Type': 'application/json' 
     } 
    } 
    // a function I have who makes the http GET 
    rest.getJSON(options, function(statusCode, res) { 
     console.log(res); 
    }); 
    pos++; 

    if(pos<arr.length) 
     setTimeout(function(){ 
      populate(arr,callback,pos); 
     },110); //a little wiggle room since setTimeout isn't exact 
    else 
     callback(); 
} 

Вы можете добавить скорость предельную функцию, но, ИМХО, он вводит излишнюю сложность. Все, что вы действительно хотите сделать, это вызвать функцию каждую десятую секунды или около того, пока вы не закончите свой список, так что сделайте это.

Это, конечно, не так легко, как альтернатива, но я поклонник простоты.

+0

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

+0

спасибо. Рад, что вы его работали в любом случае. –

Смежные вопросы