向一个API发出多个请求,该API每分钟只能处理20个请求

Make several requests to an API that can only handle 20 request a minute

本文关键字:API 请求 每分钟 20个 处理 一个      更新时间:2023-09-26

我有一个方法返回一个promise,该方法在内部调用API,该API每分钟只能有20个请求。问题是我有一个大的对象数组(大约300个),我想为每个对象调用API。

目前我有以下代码:

    const bigArray = [.....];
    Promise.all(bigArray.map(apiFetch)).then((data) => {
      ...
    });

但它无法处理时间限制。我希望我可以使用lodash中的_.chunk和_.debounce之类的东西,但我无法集中注意力。有人能帮我吗?

如果你可以使用Bluebird promise库,它内置了一个并发功能,可以让你管理一组异步操作,一次最多N个。

var Promise = require('bluebird');
const bigArray = [....];
Promise.map(bigArray, apiFetch, {concurrency: 20}).then(function(data) {
    // all done here
});

这个接口的好处是它可以保持20个请求。它将从20开始,然后每次一个结束,它就会开始另一个。因此,这可能比发送20个、等待全部完成、再发送20个等更有效…

这也以与bigArray完全相同的顺序提供结果,因此您可以确定哪个结果与哪个请求相匹配。

当然,您可以使用计数器用通用promise自己编写代码,但由于它已经构建在Bluebird库中,我建议您这样做。

Async库也有类似的并发控制,尽管它显然不是基于promise的。


这是一个只使用ES6承诺的手动编码版本,它可以保持结果顺序,并始终保持20个请求(直到剩下20个请求为止)以获得最大吞吐量:

function pMap(array, fn, limit) {
    return new Promise(function(resolve, reject) {
        var index = 0, cnt = 0, stop = false, results = new Array(array.length);
        function run() {
            while (!stop && index < array.length && cnt < limit) {
                (function(i) {
                    ++cnt;
                    ++index;
                    fn(array[i]).then(function(data) {
                        results[i] = data;
                        --cnt;
                        // see if we are done or should run more requests
                        if (cnt === 0 && index === array.length) {
                            resolve(results);
                        } else {
                            run();
                        }
                    }, function(err) {
                        // set stop flag so no more requests will be sent
                        stop = true;
                        --cnt;
                        reject(err);
                    });
                })(index);
            }
        }
        run();
    });
}   
pMap(bigArray, apiFetch, 20).then(function(data) {
    // all done here
}, function(err) {
    // error here
});

在此处进行演示:http://jsfiddle.net/jfriend00/v98735uu/

您可以每分钟发送一个20个请求的块,或者每3秒间隔一个请求(API所有者可能更喜欢后者)。

function rateLimitedRequests(array, chunkSize) {
  var delay = 3000 * chunkSize;
  var remaining = array.length;
  var promises = [];
  var addPromises = function(newPromises) {
    Array.prototype.push.apply(promises, newPromises);
    if (remaining -= newPromises.length == 0) {
      Promise.all(promises).then((data) => {
        ... // do your thing
      });
    }
  };
  (function request() {
    addPromises(array.splice(0, chunkSize).map(apiFetch));
    if (array.length) {
      setTimeout(request, delay);
    }
  })();
}

每3秒呼叫1:

rateLimitedRequests(bigArray, 1);

或者每分钟20个:

rateLimitedRequests(bigArray, 20);

如果您更喜欢使用_.chunk_.debounce1_.throttle:

function rateLimitedRequests(array, chunkSize) {
  var delay = 3000 * chunkSize;
  var remaining = array.length;
  var promises = [];
  var addPromises = function(newPromises) {
    Array.prototype.push.apply(promises, newPromises);
    if (remaining -= newPromises.length == 0) {
      Promise.all(promises).then((data) => {
        ... // do your thing
      });
    }
  };
  var chunks = _.chunk(array, chunkSize);  
  var throttledFn = _.throttle(function() {
    addPromises(chunks.pop().map(apiFetch));
  }, delay, {leading: true});
  for (var i = 0; i < chunks.length; i++) {
    throttledFn();
  }
}

1您可能想要_.throttle,因为它在延迟后执行每个函数调用,而_.debounce将多个调用分组为一个调用。请参阅本文链接自文档

Debounce:把它想象成"将多个事件分组为一";。想象一下,你回家,进入电梯,门都关上了。。。突然你的邻居出现在大厅里,试图跳上电梯。要有礼貌!为他开门:你要离开电梯了。考虑到同样的情况可能会再次发生在第三个人身上,等等……可能会推迟几分钟离开。

节流:把它看作一个阀门,它调节执行的流量。我们可以确定函数在特定时间内被调用的最大次数。所以在电梯的比喻中。。你很有礼貌地让人们进来10秒,但一旦延迟过去,你就必须离开!