Frosty
Frosty

Reputation: 51

JavaScript _.throttle can´t get called 3 times in a row?

So, I´m using the .throttle method in Javascript, to avoid spaming a function. Even if the method would be called often, there would be a certain time between these calls. Here is my code:

const _ = require('lodash');
const minTimeBetweenAlerts = 2000;

const tester = _.throttle(test, minTimeBetweenAlerts);

function test() {
   console.log('function called!');
}

for(var i = 0; i < 3; i++) {
   tester();
}

As you can see, a quite easy code. But I´ve noticed, that if tester(); would be called more than 2 times (for example 3 times in a row in a very very short amount of time). The problem is: After running my code, the output in my console would be only 2 times "function called". Has anyone a suggestion, how I can fix this problem ?

EDIT: My goal is that, the function will be called 3 times instead of 2. But I want to keep the minTime between the calls of the function

Upvotes: 0

Views: 173

Answers (1)

Ben Aston
Ben Aston

Reputation: 55729

The behavior of lodash throttle is to invoke the function, and then by default invoke it again at the end (the trailing edge) of the interval if a subsequent call has been made within the interval. That is why you get two invocations.

In the following diagram "call" means you call the throttled function, and "invoke" means the underlying function is run. Note how one of the calls is discarded:

0              1000ms           2000ms   
|______________|______________|

^        ^      ^             ^
|        |      |             |
invoke   |      |             invoke (trailing invocation)
|        |      |
|        |      |
call     call   call

What you want is a rate limiter function. I had a stab at one below, but I'd recommend using a library implementation.

function limit(f, interval) {
  const q = []
  let timeLastRun = 0
  let currentTimeoutId = null 
  
  const maybeRun = () => {
    if(!q.length) return;

    const now = performance.now()

    if((now - timeLastRun) >= interval) {
      clearTimeout(currentTimeoutId)
      currentTimeoutId = null
      timeLastRun = now
      q.pop()()
      if(q.length) {
        currentTimeoutId = setTimeout(maybeRun, interval)
      }
    }
  }
  
  const limited = (...args) => {
    q.unshift(() => f(...args))
    maybeRun()
    if(q.length && !currentTimeoutId) {
      currentTimeoutId = setTimeout(maybeRun, interval)
    }
  }
  
  return limited
}

function test() {
  console.log('function called!')
}

const tester = limit(test, 2000)
 
for(var i = 0; i < 3; i++)
  tester()

Upvotes: 2

Related Questions