2

我的问题

  • 启动 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)时,它会返回一个“请求”数组。

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

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

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

我的问题

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

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

4

1 回答 1

1

当你有一个复杂的控制流时,使用 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 个请求的速率限制器。事实上,它的执行速度比这要慢一些,因为每个批次将在请求时间 + 1 秒内执行。这非常好,并且已经满足您的初衷,但我们可以改进这一点,以挤压更多请求,以尽可能接近每秒 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 个并行请求,但它可以工作。我们当然可以继续实现更复杂的算法来平滑请求模式等,但我把它留给你的创造力和作为读者的家庭作业。

于 2020-08-25T12:46:12.483 回答