Node.js-在限制速率的同时触发多个API调用,并等待它们全部完成

问题描述

我的问题

  • 推出1000多种在线API,将API调用次数限制为10次/秒。
  • 等待所有API调用返回结果(或重试),API可能需要5秒钟才能发送数据
  • 在我的其他应用程序中使用合并的数据

我在网站上查看许多其他问题和答案时所尝试的操作

使用Promise等待一个API请求

const https = require("https");

function myRequest(param) {
  const options = {
    host: "api.xxx.io",port: 443,path: "/custom/path/"+param,method: "GET"
  }

  return new Promise(function(resolve,reject) {
    https.request(options,function(result) {
      let str = "";
      result.on('data',function(chunk) {str += chunk;});
      result.on('end',function() {resolve(JSON.parse(str));});
      result.on('error',function(err) {console.log("Error: ",err);});
    }).end();
  });
};

使用Promise.all来完成所有请求并等待它们完成

const params = [{item: "param0"},...,{item: "param1000+"}]; // imagine 1000+ items

const promises = [];
base.map(function(params){
  promises.push(myRequest(params.item));
});

result = Promise.all(promises).then(function(data) {
  // doing some funky stuff with dat
});

到目前为止,还不错

当我将API请求的数量限制为最多10个时,它起作用,因为速率限制器会启动。当我 console.log(promises)时,它会返回一个数组'request '。

我试图在不同的地方添加setTimeout,例如:

...
base.map(function(params){
  promises.push(setTimeout(function() {
    myRequest(params.item);
  },100));
});
...

但这似乎不起作用。当我 console.log(promises)时,它会返回一系列“函数

我的问题

  • 现在我被困住了...有什么想法吗?
  • 当API出现错误时,我如何构建重试

感谢您的阅读,您已经是我书中的英雄!

解决方法

当您使用async / await进行复杂的控制流时,有助于弄清流的逻辑。

让我们从以下简单算法开始,将所有内容限制为每秒10个请求:

make 10 requests

wait 1 second

repeat until no more requests

为此,以下简单的实现将起作用:

async function rateLimitedRequests (params) {
    let results = [];

    while (params.length > 0) {
        let batch = [];

        for (i=0; i<10; i++) {
            let thisParam = params.pop();
            if (thisParam) {                          // use shift instead 
              batch.push(myRequest(thisParam.item));  // of pop if you want
            }                                         // to process in the
                                                      // original order.
        }

        results = results.concat(await Promise.all(batch));

        await delayOneSecond();
    }

    return results;
}

现在,我们只需要实现一秒钟的延迟。我们可以为此简单地设置setTimeout:

function delayOneSecond() {
    return new Promise(ok => setTimeout(ok,1000));
}

这无疑将为您提供每秒仅10个请求的速率限制器。实际上,它的执行速度要慢一些,因为每个批处理将在请求时间+一秒内执行。完全可以满足您的初衷,但是我们可以改进此方法以挤压更多请求,以尽可能接近每秒精确地10个请求。

我们可以尝试以下算法:

remember the start time

make 10 requests

compare end time with start time

delay one second minus request time

repeat until no more requests

同样,我们可以使用与上面的简单代码几乎完全相同的逻辑,但只需对其进行调整即可进行时间计算:

const ONE_SECOND = 1000;

async function rateLimitedRequests (params) {
    let results = [];

    while (params.length > 0) {
        let batch = [];
        let startTime = Date.now();

        for (i=0; i<10; i++) {
            let thisParam = params.pop();
            if (thisParam) {
                batch.push(myRequest(thisParam.item));
            }
        }

        results = results.concat(await Promise.all(batch));

        let endTime = Date.now();
        let requestTime = endTime - startTime;
        let delayTime = ONE_SECOND - requestTime;

        if (delayTime > 0) {
            await delay(delayTime);
        }
    }

    return results;
}

现在,我们可以编写一个接受延迟时间的函数,而不是对一秒的延迟函数进行硬编码:

function delay(milliseconds) {
    return new Promise(ok => setTimeout(ok,milliseconds));
}

我们这里有一个简单易懂的功能,它将速率限制为尽可能接近每秒10个请求。它相当突发,因为它在每个一秒钟的周期开始时发出10个并行请求,但它可以工作。当然,我们可以继续实施更复杂的算法来平滑请求模式等,但我将其留给您的创造力以及作为读者的功课。