2013-11-15 13 views
7

Ich versuche Längen- und Breitengrad aus einer Liste von Adressen mit der Google API über ein Node.js-Skript abzurufen. Der Anruf selbst funktioniert einwandfrei, da ich aber rund 100 Adressen einreichen muss. Ich benutze eine async.forEach auf einem Array, aber die Anrufe werden zu schnell gemacht und ich bekomme den Fehler "Sie haben Ihre Rate-Limit für diese API überschritten."Führe einen ForEach wie einen Wasserfall in Async aus

Ich fand, dass die Anzahl der Anrufe auf 2500 alle 24 Stunden und maximal 10 Sekunden begrenzt ist. Während ich für die 2500 pro Tag OK bin, mache ich meine Anrufe viel zu schnell für das Ratenlimit.

Ich muss jetzt eine Funktion schreiben, wer die Anrufe genug verzögert, um das Limit nicht zu erreichen. Hier ist ein Beispiel von meinem Code:

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

Wie würden Sie vorgehen, dies zu erreichen? Ich habe versucht, mein Putting rest.getJSON in einem 100ms setTimeout aber die forEach wird für alle Reihen so schnell, dass alles beginnt die setTimeout fast zur gleichen Zeit und daher ist es nicht etwas ändern ...

The async.waterfall wie es aussieht, würde den Trick machen, aber die Sache ist, ich weiß nicht genau, wie viele Zeilen ich haben werde, also kann ich nicht alle Funktionsaufrufe fest codieren. Und um ehrlich zu sein, würde es meinen Code machen wirklich hässlich

+3

Haben Sie Rekursion versucht? Verwenden Sie den Rückruf, um ein Zeitlimit festzulegen, um die Abruffunktion nach 100 ms rekursiv aufzurufen. –

+1

Dies scheint ein generisch genug Problem, dass eine allgemeine Lösung angemessen ist. Rate-limiting ist ein gängiges Konstrukt und inline setTimeout-Hacks sind wahrscheinlich nicht der richtige Weg. Der Wasserfall kann langsamer oder schneller als nötig sein, da er nur auf die Rückkehr des letzten Anrufs wartet. Wenn ein Anruf weniger als 1/10 Sekunde dauert, bist du immer noch zu schnell. –

+0

Warum nicht etwas Einfaches wie "Parallel" mit 10 eingereihten, wenn sie abgeschlossen sind, stellen Sie sicher, dass insgesamt 10 Sekunden vergangen sind, bevor Sie den nächsten Stapel starten? – WiredPrairie

Antwort

3

Die Idee ist, dass Sie eine rateLimited Funktion erstellen können, die ähnlich wie ein throttled oder debounced Funktion wirkt, außer alle Anrufe, die nicht sofort ausführen lassen Warteschlange gestellt und laufen in Auftrag als die Rate Limit Zeitperiode abläuft.

Im Grunde erstellt es parallele 1-Sekunden-Intervalle, die sich selbst über eine erneute Neuterminierung der Zeit verwalten, aber nur bis zu perSecondLimit Intervallen zulässig sind.

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

Verbrauch:

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

Das ist eine schöne generische Funktion! Ich nahm es wie es ist, und es funktionierte wie ein Zauber! Nun, nicht genau das erste Mal, aber statt 10 habe ich es auf 5 pro Sekunde gedrosselt, und jetzt funktioniert es (mit 10 würde ich noch einige Fehler bekommen, weniger mit 9, aber fehlerfrei mit 5). Vielen Dank! –

+0

Glücklich zu helfen :) –

1

Hier ist, wie ich es hacken würde (Anmerkung: arr ist Ihr Array von Orten):

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

Sie eine Rate Begrenzungsfunktion hinzufügen könnte, aber, IMHO, führt es unnötige Komplexität ein. Alles, was Sie wirklich tun wollen, ist die Funktion alle zehn Sekunden oder so zu rufen, bis Sie mit Ihrer Liste fertig sind, also tun Sie das.

Es ist sicherlich nicht so erweiterbar wie die Alternative, aber ich bin ein Fan der Einfachheit.

+0

Ich habe versucht, herauszufinden, wie es mit einer rekursiven Funktion geht, aber nie mit etwas funktionellem kam. Ich wusste nicht, dass ich die Schleife komplett entfernen und selbst emulieren musste! Ich ging immer noch mit der obigen generischen Funktion, aber Ihre ist ein gutes Beispiel dafür, wie man das Problem mit einer rekursiven Funktion hacken kann! –

+0

danke. Ich bin froh, dass es funktioniert hat. –

Verwandte Themen