Временная очередь обещаний/дроссель

У меня есть функция request-promise, которая делает запрос к API. Я ограничен скоростью этого API, и я продолжаю получать сообщение об ошибке:

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

Я запускаю пару Promise.each параллельные циклы, которые вызывают проблему, если я запускаю только один экземпляр Promise.each все работает нормально. Внутри этих вызовов Promise.each они приводят к та же функция a с вызовом request-promise. Я хочу обернуть эту функцию другой функцией queue и установить интервал 500 миллисекунд, чтобы request не выполнялись друг за другом или параллельно, а устанавливались на это время в очереди. Дело в том, что мне все еще нужны эти обещания, чтобы получить их содержимое, даже если для получения ответа требуется довольно много времени.

Есть ли что-нибудь, что сделает это для меня? Что-то, во что я могу обернуть функцию, и она будет реагировать с заданным интервалом, а не параллельно или запускать функции одну за другой?

Обновление: Возможно, это должно быть конкретное обещание, я пытался использовать функцию подчеркивания подчеркивания.

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

person ThomasReggi    schedule 19.12.2014    source источник
comment
Можете ли вы показать нам свой реальный код, чтобы нам было с чем работать?   -  person jfriend00    schedule 19.12.2014
comment
@ jfriend00 Моя реализация довольно специфична для API. У меня есть оболочка для request, которую использует целая куча функций. Это тот момент, когда я могу затормозить. Я хочу обернуть эту оболочку, содержащую request, какой-то функцией очередей.   -  person ThomasReggi    schedule 19.12.2014
comment
Общее решение подобных проблем: spex.   -  person vitaly-t    schedule 04.10.2015


Ответы (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);

Таким образом, вам нужно сделать запросы дроссельными для всей функции - это нормально. Обещания имеют встроенную очередь.

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 обещания с использованием массива аналогичным образом.

person Benjamin Gruenbaum    schedule 19.12.2014
comment
Эффективные 2 вызова в секунду немного сложнее, чем это. Например, вы должны иметь возможность отправить 2 звонка сразу, но затем вам придется ждать секунду для третьего, если он придет сразу, но не ждать вообще, если он придет на одну секунду позже. - person jfriend00; 19.12.2014
comment
@ThomasReggi это зависит от того, насколько важна скорость - я добавил второе решение, которое фактически выполняет 2 запроса в секунду, хотя первое проще. - person Benjamin Gruenbaum; 19.12.2014
comment
@ jfriend00 Я обновил ответ - спасибо, что снова заметили. - person Benjamin Gruenbaum; 19.12.2014
comment
p = p.then(function(){ return Promise.delay(1000); }); может быть записано как p = p.delay(1000), p = p.then(function() {return turn}) может быть записано как p = p.return(turn) и т. д. Боже мой, изучите API: d - person Esailija; 19.12.2014
comment
Второй выглядит неправильно, queueId всегда undefined, и вы ждете 1 секунду в дополнение к тому времени, которое потребовалось для обработки запроса. - person Esailija; 19.12.2014
comment
Я на самом деле не могу заставить это работать, несмотря ни на что, я думаю, лучше реализовать стандартные алгоритмы. Кстати, метод экземпляра задержки требует только времени и проходит через предыдущее значение выполнения вместо того, чтобы принимать пользовательское значение. Таким образом, 1_ - person Esailija; 19.12.2014
comment
Вот если бы у меня был здесь автор библиотеки, чтобы разрешить синтаксис... :p - person Benjamin Gruenbaum; 19.12.2014
comment
О, я понимаю, так как это any промисы до того, как внутренняя часть назначит их, это не должно быть слишком сложно исправить. - person Benjamin Gruenbaum; 20.12.2014
comment
Это производство готово? Это решение работает (пока)? Он выполняет свою работу, но не соответствует приложению? Не совсем уверен в проблеме, которую описывает @Esailija. - person ThomasReggi; 20.12.2014
comment
Я использую одну из старых версий, а не дебацированную. Было бы полезно иметь здесь историю версий. - person ThomasReggi; 20.12.2014
comment
В каждом вопросе SO есть история изменений. Вероятно, вам следует использовать более новый, который является более правильным. Вы можете использовать его в продакшне, он работает — просто сначала убедитесь, что вы его понимаете — также я думаю, что он может быть красивее - person Benjamin Gruenbaum; 20.12.2014
comment
Это нормально, если 2 вызова в секунду действительно означают 2 вызова, хотя и близких друг к другу, в любую секунду. Однако, если это действительно означает 1 вызов в любой заданный период 500 мс, то при достаточно быстрых ответах сервера решение any-p-p2 все равно может спровоцировать ошибку Exceeded.... - person Roamer-1888; 21.12.2014
comment
Забыл про историю версий!! @Roamer-1888 Roamer-1888 да, мне нужно провести некоторые тесты, чтобы увидеть, какой конкретно у сервера мотыга. - person ThomasReggi; 21.12.2014
comment
Извините, у меня глаза вытаращились, когда я добрался до истории версий. ИМХО топ-публикация здесь сбивает с толку (в отличие от электронной почты). За решением для устранения дребезга трудно следовать, но я думаю, что мое, если, однако ... все еще может спровоцировать наблюдение, в равной степени относится и к нему. - person Roamer-1888; 21.12.2014
comment
Ненавижу продолжать это, но я получаю сообщение об ошибке gist.github.com/reggi/88027e0ffd6f2ef31650 - person ThomasReggi; 23.12.2014
comment
Если вам интересно, есть альтернативный ответ. - person Roamer-1888; 23.12.2014
comment
@ThomasReggi debounced возвращает функцию, которая возвращает обещание, а не само обещание, если вы хотите вызвать его, вам придется вызывать debounced()().then, а не debounced().then, поскольку вам может понадобиться несколько различных функций, от которых отказываются. Проверьте пример использования в конце ответа. - person Benjamin Gruenbaum; 23.12.2014
comment
@BenjaminGruenbaum Я создал здесь новую проблему для своей проблемы stackoverflow.com/questions/28459812/ - person ThomasReggi; 11.02.2015