2013-11-15 8 views
7

Google APIでNode.jsスクリプトを使用して住所のリストから経度と緯度を取得しようとしています。呼び出し自体は正常に動作しますが、約100個のアドレスを送信する必要があります。配列でasync.forEachを使用していますが、呼び出しが速すぎるため、「このAPIのレート制限を超過しました」というエラーが表示されます。forEachを非同期の滝のように実行します。

コール数は、24時間ごとに2500回、最大10秒間に制限されています。私は1日2500人でOKですが、私は自分の通話料金がレート制限のために速すぎるようにします。

私は今、制限に達することができないほどコールを遅らせる機能を書く必要があります。ここに私のコードのサンプルです:

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

これを達成するためにどのように進めますか?私はasync.waterfallはそれのように見えます

...それは同時に、ほぼすべてのsetTimeoutを開始し、したがって、それは何も変更しないように速く、すべての行を私の100msのsetTimeout内部rest.getJSONが、forEach繰り返し処理を入れてみましたそのトリックを行うだろうが、私はどのくらいの行を持っているのか分からないので、すべての関数呼び出しをハードコードすることはできません。そして、正直に言うと、それは私のコードが本当に醜くなるだろう

+3

再帰を試みましたか?コールバックを使用して、100ms後に再帰的にfetch関数を呼び出すタイムアウトを設定します。 –

+1

これは、一般的な解決策が適切である十分な一般的な問題のようです。レート制限は一般的な構造であり、インラインsetTimeoutハックはおそらく正しい方法ではありません。滝は最後の呼び出しが戻るのを待つため、必要以上に遅くなったり、速くなったりする可能性があります。通話が1秒間に10分の1以下になると、あなたはまだ速すぎます。 –

+0

10個のキューを持つ 'parallel'のような単純なものではなく、次のバッチを開始する前に合計10秒が経過していることを確認してください。 – WiredPrairie

答えて

3

アイデアは、あなたがキューイングされますすぐに実行し、中には実行されません任意の呼び出しを除いて、多くのthrottledまたはdebounced機能のような役割を果たしrateLimited機能を作成することができるということですレート制限期間の満了に応じて注文します。

基本的には、タイマーの再スケジューリングによって自己管理するパラレル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の代わりに1秒あたり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(); 
} 

けど、 IMHO、それは不必要な複雑さをもたらします。あなたが本当にやりたいことは、あなたのリストが完了するまで関数を10分の1秒ごとに呼び出すことです。そうしてください。

これは確かに代替として拡張可能ではありませんが、私はシンプルさのファンです。

+0

私は再帰的な関数でそれを行う方法を理解しようとしましたが、何か機能的に出てきませんでした。ループを完全に削除して自分でエミュレートしなければならないことは分かりませんでした。私はまだ上記のジェネリック関数を使いましたが、あなたは再帰関数で問題をハックする方法の良い例です!ありがとう。 –

+0

ありがとう。あなたがそれをどちらかの方法で働いてうれしいです。 –

関連する問題