Выполнение троттлинг функция


У меня есть JavaScript функция, которая вызывает мой бэкенд API с помощью AJAX, скажем, updateDataUsingAjax. Это ресурсоемкий процесс, и я не желаю, чтобы эта функция будет вызываться много раз в короткий промежуток времени.

Допустим, я намерен разрешить только 3 запроса в каждые 5 секунд. Если функция вызывается более того, ему следует бросить сообщение об ошибке.

Что-то похожее на то, что произойдет, если вы нажмете на флаг кнопку слишком много раз на переполнение стека (или здесь):


Мой первоначальный подход к решению этой проблемы было следующим:

  • Отслеживать количество выполнений и первый экземпляр исполнения функции.

  • Выполнять логические функции если,

    • Количество выполнений меньше допустимого (3 запросов)

    • Или, если больше времени прошло, чем настроили время (5 секунд) - в таком случае обновить метку времени первого исполнения, а также.

  • Обновление граф выполнения, если функция будет разрешено выполняться.

Такой подход был в порядке, но я понял, что там может быть нужно реализовать такую логику для нескольких процессов, с разными лимитами. Поэтому я решил написать "генератор функции регулирования", что бы взять

  1. функция должна быть выполнена,
  2. максимальное количество запросов,
  3. и период, для которых регулирование должно применяться.

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


function throttledFunctionGenerator(func, limit, interval) {
  var count = 1;

  return function(arg) {
    // Run the function only if it hasn't yet exceeded the limit.
    if (count <= limit) {

      // Execute the function.
      // Note: Exception handling is not necessary as setTimeout is used.
      setTimeout(func, 1, arg);

      // If this is the first request, schedule the counter to be reset after specified interval.
      if (count === 1) {
        setTimeout(function(arg) {
          // Reset the counter.
          count = 1;
        }, interval * 1000, arg);
      }

      // Increment the count.
      count += 1;
    } else {
      throw "You may perform this action only " + limit + " times in " + interval + " second(s).";
    }
  };
}

Так что если у меня есть функция doSomething это должно называться не более 3 раз в 10 секунд, я мог бы обернуть его использования,

var doSomethingThrottled = throttledFunctionGenerator(doSomething, 3, 5);

Поэтому, когда я собираюсь выполнить следующий код, четвертый вызов должен бросить сообщение об ошибке:

doSomethingThrottled(obj1);
doSomethingThrottled(obj2);
doSomethingThrottled(obj3);
doSomethingThrottled(obj4); // Throws an error.

Вот демо на сайт CodePen , где вы можете увидеть это в действии.

var btn = document.getElementById("btn");
var btn2 = document.getElementById("btn2");
var logArea = document.getElementById("logArea");

function log(message) {
  logArea.classList.add("yellow");
  logArea.innerText += message + "\n";

  setTimeout(function() {
    logArea.classList.remove("yellow");
  }, 500);
}

function throttledFunctionGenerator(func, limit, interval) {
  var count = 1;

  return function(arg) {
    // Run the function only if it hasn't yet exceeded the limit.
    if (count <= limit) {

      // Execute the function in next event loop.
      // Note: Exception handling is not necessary as setTimeout is used.
      setTimeout(func, 1, arg);

      // If this is the first request, schedule the counter to be reset after specified interval.
      if (count === 1) {
        setTimeout(function(arg) {
          // Reset the counter.
          count = 1;
        }, interval * 1000, arg);
      }

      // Increment the count.
      count += 1;
    } else {
      throw "You may perform this action only " + limit + " times in " + interval + " second(s).";
    }
  };
}

var thriceEveryFiveSeconds = throttledFunctionGenerator(function(msg) {
  log("[Thrice]: " + msg);
}, 3, 5);
var twiceEveryTenSeconds = throttledFunctionGenerator(function(msg) {
  log("[Twice]: " + msg);
}, 2, 10);

btn.addEventListener("click", function(event) {
  var date = new Date();
  thriceEveryFiveSeconds(`Clicked: ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`);
});

btn2.addEventListener("click", function(event) {
  var date = new Date();
  twiceEveryTenSeconds(`Clicked: ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`);
});
.yellow {
  background-color: yellow;
}
<button id="btn">Thrice every 5 seconds</button>
<button id="btn2">Twice every 10 seconds</button>
<pre id="logArea"></pre>


Мне известны следующие ограничения:

  • setTimeout не является точным.

  • Код в его текущем состоянии работает только для функций, которые принимают только один аргумент. Но я верю, что он может быть легко расширен для работы с любым количеством аргументов, с помощью распространения на ES6 синтаксиса.

Вы видите какие-либо ошибки с кодом, или любой области улучшения?



848
10
задан 26 марта 2018 в 04:03 Источник Поделиться
Комментарии
2 ответа

Ваша функция контролирует средняя скорость не показатель.

Ваша функция может позволить больше функций, чем требуется в заданном интервале.

Скажем, у вас есть 3 звонка за 5 секунд. Первый звонок запускает таймер, а затем в 4.9 секунд до истечения указанного срока, вы звоните два раза функцию. Потом просто через интервал времени истекает (@5.1 секунд)вызове функции более 3 раз. Таким образом, вы назвали функцию 5 раз меньше, чем за секунду. Максимальная ставка Вы можете вызвать функцию limit * 2 - 1 в время для выполнения каждой.

Вас поддерживать общий средний уровень, но не ограничение скорости для каждого интервала.

Решение для отслеживания времени каждая функция начинается, при добавлении времени в очереди. Каждый раз, когда вы получите еще один запрос на выполнение функции, которую вы проверьте, если любой из предыдущих запусков больше, то лимит назад. Если таким образом удалить его из очереди. Если очередь меньше, чем ограничить время выполнения, то вы можете запустить функцию.

function throttler (func, count, seconds) {
const queue = [];
return function(...args) {
var now = performance.now();
while (queue.length && queue[0] < now) { queue.shift() }
if(queue.length < count){
queue.push(now + seconds * 1000);
func(...args);
}else{
// I would just return a false rather than use error to communicate
throw new Error("Exe count overrun");
}
}
}

Или вы можете использовать обещание управлять функции асинхронный

function throttler (func, count, seconds) {
const queue = [];
return function(...args) {
return new Promise((runOk, timeout) =>
var now = performance.now();
while (queue.length && queue[0] < now) { queue.shift() }
if (queue.length < count) {
queue.push(now + seconds * 1000);
setTimeout(() => runOK(func(...args)) ,0);
} else {
timeout(new Error("Rate to high"));
}
});
}
}

// used
const throttled = throttler (test, 5, 5);

throttled(1,2,3)
.then(returnData => {/*do somethin*/});
.catch(error => {/*handle error*/})

Передача аргументов

Разбор аргументов с помощью остальных параметров и распространения в ... опе

Например

function callFunctionWith(...arg) {  // rest 
theFunctionTakesArgs(...args); // spread
}
function theFunctionTakesArgs(a, b, c, d, e){
console.log(a, b, c, d, e);
}

callFunctionWith(1, 2, 3, 4, 5);

6
ответ дан 26 марта 2018 в 05:03 Источник Поделиться

Первая проблема, которую я вижу, заключается в том, что ваш тариф-лимит логика на стороне клиента. Вы никогда не должны полностью полагаться на стороне клиента код, они легко обошли. Ничто не остановит меня от бесконечно curlющих, что в конечной точке прямо или код, который вызывает Аякс напрямую.

Вместо того, чтобы реализовать эту лимитирующей логики на стороне сервера. Когда пользователь достигает некоторого порогового значения, сервер отвечает на HTTP 503 (служба недоступна), пока очередь не очистится. Делаете ли вы это во всем мире, на операцию или на одного пользователя, составляет до тебя. Тогда все пользовательского интерфейса делает это сделать соответственно, был ли запрос успешно или получил 503.

Теперь, если на самом деле есть серверная часть на месте и это просто ваш код на стороне клиента, первое, что озадачивает меня, является использование таймера. Вы на самом деле не нужен таймер. Нужно просто запомнить тайминг успешно операции, и посчитайте, сколько из них в полете в последние несколько секунд.

const throttle = (fn, count, interval) => {
const inFlight = []

return (...args) => {
const now = Date.now()

// Purge older timestamps
for(let i = inFlight.length; i--;){
if(inFlight[i] > now) continue
inFlight.splice(i, 1)
}

// Check the length of ops in flight
if (inFlight.length < count){
// Persist this call's timestamp.
inFlight.push(now + interval)

// Return what the function would normally have returned.
return fn(...args)
} else {
throw new Error('Too many in flight')
}
}
}

3
ответ дан 26 марта 2018 в 06:03 Источник Поделиться