【问题标题】:Timed promise queue / throttle定时承诺队列/油门
【发布时间】:2015-02-18 02:25:14
【问题描述】:

我有一个向 API 发出请求的 request-promise 函数。我受到此 API 的速率限制,并且不断收到错误消息:

Exceeded 2 calls per second for api client. Reduce request rates to resume uninterrupted service.

我正在并行运行几个Promise.each 循环,这会导致问题,如果我只运行Promise.each 的一个实例,一切运行正常。在这些Promise.each 调用中,它们导致与request-promise 调用相同的函数a。我想用另一个queue 函数包装这个函数,并将间隔设置为500 毫秒,这样request 就不会一个接一个地创建,也不会并行创建,而是设置为队列中的那个时间。问题是我仍然需要这些承诺来获取它们的内容,即使需要很长时间才能得到响应。

有什么可以为我做这件事的吗?我可以包装一个函数,它会以设定的时间间隔响应,而不是并行或一个接一个地触发函数?

更新:也许确实需要promise具体,我尝试使用下划线的油门功能

var debug = require("debug")("throttle")
var _ = require("underscore")
var request = require("request-promise")

function requestSite(){
  debug("request started")
  function throttleRequest(){
    return request({
      "url": "https://www.google.com"
    }).then(function(response){
      debug("request finished")
    })
  }
  return _.throttle(throttleRequest, 100)
}

requestSite()
requestSite()
requestSite()

我得到的只是这个:

$ DEBUG=* node throttle.js 
throttle request started +0ms
throttle request started +2ms
throttle request started +0ms

【问题讨论】:

  • 您能否向我们展示一些您的实际代码,以便我们可以使用?
  • @jfriend00 我的实现非常特定于 API。我所拥有的是request 的包装器,一大堆函数都在使用它。这就是我可以节制的点。我想用某种排队功能包装包含request 的包装器。
  • 此类问题的通用解决方案:spex.

标签: javascript queue promise bluebird rate-limiting


【解决方案1】:

更新

最后一个答案是错误的,这是可行的,但我仍然认为我可以做得更好:

// call fn at most count times per delay.
const debounce = function (fn, delay, count) {
    let working = 0, queue = [];
    function work() {
        if ((queue.length === 0) || (working === count)) return;
        working++;
        Promise.delay(delay).tap(() => working--).then(work);
        let {context, args, resolve} = queue.shift();
        resolve(fn.apply(context, args));
    }
    return function debounced() {
        return new Promise(resolve => {
            queue.push({context: this, args: arguments, resolve});
            if (working < count) work();
        });
    };
};

function mockRequest() {
    console.log("making request");
    return Promise.delay(Math.random() * 100);
}

var bounced = debounce(mockRequest, 800, 5);
for (var i = 0; i < 5; i++) bounced();
setTimeout(function(){
    for (var i = 0; i < 20; i++) bounced();
},2000);

因此,您需要在整个函数范围内限制请求 - 这很好。 Promise 几乎内置了队列。

var p = Promise.resolve(); // our queue

function makeRequest(){
    p = p.then(function(){ // queue the promise, wait for the queue
        return request("http://www.google.com");
    });
    var p2 = p; // get a local reference to the promise
    // add 1000 ms delay to queue so the next caller has to wait 
    p = p.delay(1000); 
    return p2;
};

现在 makeRequest 调用将至少相隔 1000 毫秒。

jfriend 指出您每秒需要两个请求,而不是单个请求 - 这与第二个队列一样容易解决:

var p = Promise.resolve(1); // our first queue
var p2 = Promise.resolve(2); // our second queue

function makeRequest(){

    var turn = Promise.any([p, p2]).then(function(val){ 

        // add 1000 ms delay to queue so the next caller has to wait 
        // here we wait for the request too although that's not really needed, 
        // check both options out and decide which works better in your case
        if(val === 1){
            p = p.return(turn).delay(1, 1000);
        } else {
            p2 = p2.return(turn).delay(1, 1000); 
        }
        return request("http://www.google.com");
    });

    return turn; // return the actual promise
};

这可以推广到n 类似地使用数组的承诺

【讨论】:

  • 高效的 2 次调用/秒比这更复杂一些。例如,您应该能够立即发送 2 个呼叫,但如果第三个呼叫立即到来,则必须等待一秒钟,但如果一秒钟后到来,则根本不等待。
  • @jfriend00 我已经更新了答案 - 感谢您再次注意到。
  • p = p.then(function(){ return Promise.delay(1000); }); 可以写成p = p.delay(1000)p = p.then(function() {return turn}) 可以写成p = p.return(turn) 等等.. jesus man 学习api:d
  • 第二个看起来不正确,queueId 始终是 undefined,除了处理请求所需的时间外,您还要等待 1 秒
  • 我实际上无论如何都无法让它工作,我想最好实现标准算法。顺便说一句,延迟实例方法只需要时间并通过先前的履行值而不是采用自定义值。所以.delay(1000).return(1)
猜你喜欢
  • 1970-01-01
  • 2018-11-28
  • 2014-12-29
  • 2018-11-03
  • 1970-01-01
  • 2014-03-14
  • 1970-01-01
  • 2017-10-22
  • 2019-05-15
相关资源
最近更新 更多