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

Nodejs - Fire multiple API calls while limiting the rate and wait until they are all done

我的问题

我在网站上查看许多不同的问题和答案时尝试过的方法

使用 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) 时,它返回一个 'function'

的数组

我的问题

感谢你的阅读,你已经是我心中的英雄了!

当你有一个复杂的 control-flow 时,使用 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 个并行请求,但它有效。我们当然可以继续实施更复杂的算法来平滑请求模式等,但我将其留给您的创造力并作为 reader.

的家庭作业。