简体   繁体   English

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

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

My issues我的问题

  • Launch 1000+ online API that limits the number of API calls to 10 calls/sec.启动 1000 多个在线 API,将 API 调用次数限制为 10 次调用/秒。
  • Wait for all the API calls to give back a result (or retry), it can take 5 sec before the API sends it data等待所有 API 调用返回结果(或重试),在 API 发送数据之前可能需要 5 秒
  • Use the combined data in the rest of my app在我的应用程序的其余部分使用组合数据

What I have tried while looking at a lot of different questions and answers here on the site我在网站上查看许多不同的问题和答案时所尝试的

Use promise to wait for one API request使用 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();
  });
};

Use Promise.all to do all the requests and wait for them to finish使用 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
});

So far so good, sort of到目前为止一切顺利,有点

It works when I limit the number of API requests to a maximum of 10 because then the rate limiter kicks in. When I console.log(promises) , it gives back an array of 'request'.当我将 API 请求的数量限制为最多 10 个时它会起作用,因为速率限制器会启动。当我console.log(promises) 时,它会返回一个“请求”数组。

I have tried to add setTimeout in different places, like:我尝试在不同的地方添加 setTimeout,例如:

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

But that does not seem to work.但这似乎不起作用。 When I console.log(promises) , it gives back an array of 'function'当我console.log(promises) 时,它会返回一个“函数”数组

My questions我的问题

  • Now I am stuck ... any ideas?现在我被卡住了......有什么想法吗?
  • How do I build in retries when the API gives an error当 API 出现错误时如何构建重试

Thank you for reading up to hear, you are already a hero in my book!感谢您阅读收听,您已经是我书中的英雄!

When you have a complicated control-flow using async/await helps a lot to clarify the logic of the flow.当你有一个复杂的控制流时,使用 async/await 有助于澄清流的逻辑。

Let's start with the following simple algorithm to limit everything to 10 requests per second:让我们从以下简单算法开始,将所有内容限制为每秒 10 个请求:

make 10 requests

wait 1 second

repeat until no more requests

For this the following simple implementation will work:为此,以下简单实现将起作用:

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;
}

Now we just need to implement the one second delay.现在我们只需要实现一秒延迟。 We can simply promisify setTimeout for this:我们可以简单地为此承诺 setTimeout :

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

This will definitely give you a rate limiter of just 10 requests each second.这肯定会给你一个每秒只有 10 个请求的速率限制器。 In fact it performs somewhat slower than that because each batch will execute in request time + one second.事实上,它的执行速度比这要慢一些,因为每个批次都将在请求时间 + 一秒内执行。 This is perfectly fine and already meet your original intent but we can improve this to squeeze a few more requests to get as close as possible to exactly 10 requests per second.这非常好,并且已经满足您的原始意图,但我们可以改进它以压缩更多请求,以尽可能接近每秒 10 个请求。

We can try the following algorithm:我们可以尝试以下算法:

remember the start time

make 10 requests

compare end time with start time

delay one second minus request time

repeat until no more requests

Again, we can use almost exactly the same logic as the simple code above but just tweak it to do time calculations:同样,我们可以使用与上面简单代码几乎完全相同的逻辑,但只需调整它以进行时间计算:

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;
}

Now instead of hardcoding the one second delay function we can write one that accept a delay period:现在,我们可以编写一个接受延迟周期的函数,而不是硬编码一秒延迟函数:

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

We have here a simple, easy to understand function that will rate limit as close as possible to 10 requests per second.我们这里有一个简单易懂的函数,它将速率限制为尽可能接近每秒 10 个请求。 It is rather bursty in that it makes 10 parallel requests at the beginning of each one second period but it works.它相当突发,因为它在每一秒周期开始时发出 10 个并行请求,但它有效。 We can of course keep implementing more complicated algorithms to smooth out the request pattern etc. but I leave that to your creativity and as homework for the reader.我们当然可以继续实现更复杂的算法来平滑请求模式等,但我把它留给你的创造力和读者的作业。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM