像异步中的瀑布一样执行forEach

时间:2013-11-15 19:16:12

标签: javascript node.js asynchronous google-api

我正在尝试通过Node.js脚本从Google API的地址列表中检索经度和纬度。呼叫本身工作正常,但因为我有大约100个地址要提交。我在数组上使用async.forEach,但调用速度太快,我收到错误“您已超出此API的速率限制。”

我发现每24小时呼叫次数限制为2500次,每秒最多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,因此它不会什么都不改......

async.waterfall看起来好像可以做到这一点,但问题是我不知道我将拥有多少行,所以我无法对所有函数调用进行硬编码。说实话,这会让我的代码变得非常丑陋

2 个答案:

答案 0 :(得分:3)

我们的想法是你可以创建一个rateLimited函数,其作用与throttleddebounced函数非常相似,但任何不立即执行的调用都会排队并按顺序运行因为费率限制时间期限到期。

基本上,它创建并行的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();
}

答案 1 :(得分: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();
}

你可以添加速率限制功能,但是,恕我直言,它引入了不必要的复杂性。你真正想做的就是每隔十分之一秒左右调用一次这个函数,直到你完成了你的列表,所以这样做。

它肯定不像其他选择那样可扩展,但我是简单的粉丝。