Javascript 在异步中像瀑布一样执行forEach

Javascript 在异步中像瀑布一样执行forEach,javascript,node.js,asynchronous,google-api,Javascript,Node.js,Asynchronous,Google Api,我正试图通过Node.js脚本使用Google API从地址列表中检索经度和纬度。这个电话本身运作良好,但因为我有大约100个地址要提交。我在数组上使用了async.forEach,但是调用速度太快,我得到错误“您已经超过了此API的速率限制。” 我发现每24小时的通话次数限制在2500次,每秒最多10次。虽然我一天2500英镑还行,但我打电话的速度太快,超出了费率限制 我现在必须编写一个函数,它将延迟调用到不达到限制的程度。以下是我的代码示例: async.forEach(final_json

我正试图通过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
放在一个100ms
setTimeout
内,但是
forEach
迭代所有行的速度非常快,几乎同时启动所有
setTimeout
,因此它不会改变任何东西


async.failter
看起来可以解决这个问题,但问题是我不知道我将有多少行,所以我不能硬编码所有的函数调用。老实说,这会让我的代码变得非常难看

这个想法是,你可以创建一个
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();
}
函数此函数将被称为oofast(){}
var limitedVersion=费率限制(10,此函数将被称为Toofast);
//10个电话将立即启动,然后随着计时器过期
//对于这些电话中的每一个,都将在其所在位置启动一个新的电话。
对于(变量i=0;i<100;i++){
有限版本();
}

以下是我将如何破解它(注意:
arr
是您的位置数组):

函数填充(arr、回调、pos){
如果(位置类型==“未定义”)
pos=0;
var path='/maps/api/geocode/json?address='+encodeURIComponent(arr[pos].main_address)+'&sensor=false';
console.log(路径);
变量选项={
主持人:“maps.googleapis.com”,
港口:80,
路径:路径,
方法:“GET”,
标题:{
“内容类型”:“应用程序/json”
}
}
//我有一个函数,它使http
getJSON(选项,函数(状态码,res){
控制台日志(res);
});
pos++;

如果(POS您是否尝试过递归?使用回调设置一个超时,以便在100毫秒后递归调用fetch函数。这似乎是一个足够普遍的问题,因此有一个通用的解决方案是合适的。速率限制是一个常见的构造,内联setTimeout黑客可能不是正确的方法。瀑布可能比必要的更慢或更快y、 因为它所做的就是等待最后一个呼叫返回。如果一个呼叫花费不到1/10秒,你仍然太快了。为什么不做一些简单的事情,比如
parallel
10个队列,当它们完成时,确保在开始下一批之前总共过了10秒?这是一个简洁的版本。我一直在思考gh和编码循环w/125ms wait+一个尚未返回结果的bucket。循环将在bucket中放置项目,http回调将删除这些项目。不要让bucket变大超过10,也不要将超过8/秒的时间推入bucket。完成后,bucket为空(等待,等待…)执行您的命令“所有完成的函数”这是一个漂亮的通用函数!我照原样做了,它工作起来很有魅力!好吧,这不是第一次,但我将它限制为每秒5次,现在它工作了(10次时我仍然会出现一些错误,9次时错误较少,5次时则完美无缺)。谢谢!我试图找出如何使用递归函数来实现它,但从来没有提出过功能性的东西。我没有意识到我必须完全删除循环并自己模拟它!我仍然使用上面的泛型函数,但你的例子很好地说明了如何使用递归函数解决问题!
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();
}