异步执行 forEach,就像瀑布一样



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

我发现呼叫次数限制为每 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,因此它不会改变任何东西......

async.waterfall看起来可以解决问题,但问题是我不知道我将确切地有多少行,所以我无法对所有函数调用进行硬编码。老实说,这会让我的代码变得非常丑陋

这个想法是,您可以创建一个 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();
}

以下是我破解它的方法(注意: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();
}

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

它当然不像替代方案那样可扩展,但我是简单性的粉丝。

相关内容

  • 没有找到相关文章

最新更新