120

I am looking for a simple throttle in JavaScript. I know libraries like lodash and underscore have it, but only for one function it will be overkill to include any of those libraries.

I was also checking if jQuery has a similar function - could not find.

I have found one working throttle, and here is the code:

function throttle(fn, threshhold, scope) {
  threshhold || (threshhold = 250);
  var last,
      deferTimer;
  return function () {
    var context = scope || this;

    var now = +new Date,
        args = arguments;
    if (last && now < last + threshhold) {
      // hold on to it
      clearTimeout(deferTimer);
      deferTimer = setTimeout(function () {
        last = now;
        fn.apply(context, args);
      }, threshhold);
    } else {
      last = now;
      fn.apply(context, args);
    }
  };
}

The problem with this is: it fires the function once more after the throttle time is complete. So let's assume I made a throttle that fires every 10 seconds on keypress - if I do keypress 2 times, it will still fire the second keypress when 10 seconds are completed. I do not want this behavior.

Yves M.
  • 29,855
  • 23
  • 108
  • 144
Mia
  • 6,220
  • 12
  • 47
  • 81
  • 3
    1. jQuery has a plugin http://benalman.com/projects/jquery-throttle-debounce-plugin/ 2. Why not just use underscore/lodash's throttle implementation? – Oleg Nov 22 '14 at 14:12
  • @Oleg is it possible to use only the throttle without importing the whole lib? – Mia Nov 22 '14 at 14:14
  • 1
    Could you set up an example, or at least explain the use case a little better? Generally a keypress throttle is pretty simple to set up, something like this -> **http://jsfiddle.net/a3w6pLbj/1/** – adeneo Nov 22 '14 at 14:16
  • 1
    https://github.com/jashkenas/underscore/blob/master/underscore.js#L754 – Oleg Nov 22 '14 at 14:16
  • 1
    If your bundler do not have [`tree-shaking` capabilities](https://webpack.js.org/guides/tree-shaking/) still you can use import like `import throttle from 'lodash/throttle'`. In this way, only one function will be imported – Shivam Jha Aug 30 '21 at 15:23

24 Answers24

142

I would use the underscore.js or lodash source code to find a well tested version of this function.

Here is the slightly modified version of the underscore code to remove all references to underscore.js itself:

// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
function throttle(func, wait, options) {
  var context, args, result;
  var timeout = null;
  var previous = 0;
  if (!options) options = {};
  var later = function() {
    previous = options.leading === false ? 0 : Date.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };
  return function() {
    var now = Date.now();
    if (!previous && options.leading === false) previous = now;
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
};

Please note that this code can be simplified if you don't need all the options that underscore support.

Please find below a very simple and non-configurable version of this function:

function throttle (callback, limit) {
    var waiting = false;                      // Initially, we're not waiting
    return function () {                      // We return a throttled function
        if (!waiting) {                       // If we're not waiting
            callback.apply(this, arguments);  // Execute users function
            waiting = true;                   // Prevent future invocations
            setTimeout(function () {          // After a period of time
                waiting = false;              // And allow future invocations
            }, limit);
        }
    }
}

Edit 1: Removed another reference to underscore, thx to @Zettam 's comment

Edit 2: Added suggestion about lodash and possible code simplification, thx to @lolzery @wowzery 's comment

Edit 3: Due to popular requests, I added a very simple, non-configurable version of the function, adapted from @vsync 's comment

Sean
  • 6,873
  • 4
  • 21
  • 46
Clément Prévost
  • 8,000
  • 2
  • 36
  • 51
  • That was only dates I believe? Worked perfectly after the edit! (not all _. referances are removed in your version) – Mia Nov 22 '14 at 14:25
  • 68
    doesn't look simple to me. This is a [good example](https://jsfiddle.net/jonathansampson/m7G64/) of simple – vsync Mar 30 '16 at 11:27
  • 12
    Indeed, this is not simple. But it's production ready and open-source. – Clément Prévost Mar 30 '16 at 16:05
  • 1
    FWIW, Date.now() doesn't work on ie8 and below. The cross-browser alternative is `new Date().getTime()`. And if you care really a lot about performance what you can do is `(Date.now || function(){ return new Date().getTime(); } )()`. – David Sep 08 '16 at 08:09
  • 18
    One of the reasons this isn't as simple as the one @vsync linked to is because it supports trailing calls. With this one, if you called the resulting function twice, it will result in two calls to the wrapped function: once immediately and once after the delay. In the one vsync linked to, it will result in a single, immediate call, but none after the delay. In many cases, receiving the trailing call is very important in order to get the last viewport size or whatever it is you're trying to do. – Aaronius Dec 03 '16 at 18:33
  • 3
    Please do not ever use this. I don't intend to be arrogant. Rather, I just intend to be practical. This answer is waaay more complicated than it needs to be. I posted up a separate answer to this question that does all of this and more in far fewer lines of code. – Jack G Apr 07 '18 at 01:32
  • You are right in the sense that my main recommendation is to use a library and not re-invent the wheel. The lodash code also provide some options that you may remove to gain in complexity – Clément Prévost Apr 08 '18 at 09:11
  • 1
    @vsync, I've been searching a simple throttle function (like the one you link to) for a while and have found all your comments "this is debounce, not throttle" hilarious. The amount of wrong answers is huge. This is a right answer and your comment is even better. Thank you – Nico Dec 28 '18 at 09:27
  • `arguments` is not even defined, and treated as a global. How is this even working? – Nico Dec 28 '18 at 09:28
  • 4
    @Nico the `arguments` object is always defined inside any function that is not an arrow function: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments – Clément Prévost Dec 28 '18 at 11:39
  • 1
    I've been programming for a while, but every time I come here I discover something new. Thank you @ClémentPrévost – Nico Dec 28 '18 at 12:53
  • 1
    @vsync the simple example shown by you ignore the last call, which could be a problem, e.g mouse movement for a resize , check https://codeburst.io/throttling-and-debouncing-in-javascript-b01cad5c8edf – Qiulang May 06 '19 at 14:33
  • This did NOT work for me. No errors, but also does not call the throttled function at all. Using Typescript 3.6. – Tigerware Sep 18 '19 at 10:05
  • Can you please provide your TS adaptation @BluE? – Clément Prévost Sep 18 '19 at 11:13
  • @ClémentPrévost I plugged in your simple code into this question, but it's not passing. Do you have any ideas? https://bigfrontend.dev/problem/implement-basic-throttle – techguy2000 Sep 01 '20 at 04:01
  • 1
    @techguy2000 Good remark. This is because the BigFrontend challenge expects the throttle function to execute the last throttled call once the throttle limit is passed. This simple solution drops the call if we are throtteling and will not call it back later. Both solutions apply to various real world problems. Here is the version you are looking for: https://gist.github.com/prevostc/dcb395fc8ae4cf094e42d262e77746bd – Clément Prévost Sep 01 '20 at 09:32
  • Thanks so much @ClémentPrévost! – techguy2000 Sep 02 '20 at 05:50
  • Does the short version work with functions that return a value? I don't see any return statement – diedu Mar 25 '21 at 19:16
  • @diedu It does not, but the underscore/lodash version do. They implement some sort of return value caching to make it work – Clément Prévost Mar 27 '21 at 00:48
  • why aren't we using `clearTimeout()` otherwise there would be many unused timeout id in stack? instead of `var waiting = false; ` can we use `let timerId;` in first line and assign `timerid = setTimeout(...);` – xkeshav Oct 14 '21 at 17:33
33

What about this?

function throttle(func, timeFrame) {
  var lastTime = 0;
  return function () {
      var now = Date.now();
      if (now - lastTime >= timeFrame) {
          func();
          lastTime = now;
      }
  };
}

Simple.

You may be interested in having a look at the source.

Dominic
  • 62,658
  • 20
  • 139
  • 163
smartmouse
  • 13,912
  • 34
  • 100
  • 166
  • 3
    This is the cleanest minimal implementation on the page. – Lawrence Dol Mar 06 '20 at 03:16
  • 4
    For me this only works with `Date.now()` rather than `new Date()` – Ian Jones Jul 27 '20 at 21:08
  • I also have a warning in TS when doing `now - lastTime` because `now` is a Date. Replacing by `Date.now()` to get a number seems legit. – Vadorequest Feb 17 '21 at 09:09
  • Any downside to this approach compared to using `setTimeout`? – Vic Mar 29 '21 at 00:20
  • 2
    @Vic the last call to your function is not guaranteed – Ricky Boyce Oct 20 '21 at 20:59
  • How can we ensure a final call to `func` for times when it doesn't get called? E.g. `timeFrame` is 5000, but you call twice within `3000`; the first call is handled but the second is skipped; how can we automatically call `func` for that second call? – daCoda Oct 10 '22 at 07:30
14

callback: takes the function that should be called

limit: number of times that function should be called within the time limit

time: time span to reset the limit count

functionality and usage: Suppose you have an API that allows user to call it 10 times in 1 minute

function throttling(callback, limit, time) {
    /// monitor the count
    var calledCount = 0;

    /// refresh the `calledCount` varialbe after the `time` has been passed
    setInterval(function(){ calledCount = 0 }, time);

    /// creating a closure that will be called
    return function(){
        /// checking the limit (if limit is exceeded then do not call the passed function
        if (limit > calledCount) {
            /// increase the count
            calledCount++;
            callback(); /// call the function
        } 
        else console.log('not calling because the limit has exceeded');
    };
}
    
//////////////////////////////////////////////////////////// 
// how to use

/// creating a function to pass in the throttling function 
function cb(){
    console.log("called");
}

/// calling the closure function in every 100 milliseconds
setInterval(throttling(cb, 3, 1000), 100);
Artur Carvalho
  • 6,901
  • 10
  • 76
  • 105
Vikas Bansal
  • 10,662
  • 14
  • 58
  • 100
  • 6
    @lolzerywowzery It's not like your answer is less complicated than it needs to be – Denny Oct 07 '17 at 13:02
  • 3
    I would suggest firing the callback like so: `callback(...arguments)` to keep the original arguments. very handy – vsync Jun 23 '18 at 15:19
  • This should be the accepted answer. Simple and easy. – Gaurang Tandon Jun 30 '18 at 10:53
  • @Denny This answer waisted tremendous browser resources. It starts an entire new Intervalling function every single handler it creates. Even after you remove the event listener, the continuous polling drains computer resources, leading to high memory usage, freeze-ups, and page bricks. – Jack G Jul 26 '18 at 00:21
  • 6
    Please do not ever use this answer in production code. This is very opitome of poor programming. Say you have 1000 buttons on your page (which may sound like a lot but think again: buttons hide everywhere: in popups, submenus, panels, etc.) and want to limit each one to fire at most once every 200 seconds Now, since they would likely be all initiated at the same time, every 333 milliseconds (or 3 times a second), there would be a massive lag spike when all of these timers need to check again. This answer completely abuses `setInterval` for purposes it was not intended to do. – Jack G Oct 12 '18 at 12:13
12

Adding to the discussion here (and for more recent visitors), if the reason for not using the almost de facto throttle from lodash is to have a smaller sized package or bundle, then it's possible to include only throttle in your bundle instead of the entire lodash library. For example in ES6, it would be something like:

import throttle from 'lodash/throttle';

Also, there is a throttle only package from lodash called lodash.throttle which can be used with a simple import in ES6 or require in ES5.

Divyanshu Maithani
  • 13,908
  • 2
  • 36
  • 47
  • 5
    Checked the code. it is using 2 files imports, so this means you will need 3 files for a simple throttle function. a bit of an overkill I would say, especially if someone (like myself) needs a throttle function for a ~200 lines of code program. – vsync Jun 23 '18 at 15:00
  • 6
    Yeah, it internally uses `debounce` and `isObject`, the whole bundle size comes to around [2.1KB minified](https://bundlephobia.com/result?p=lodash.throttle@4.1.1). I suppose, doesn't make sense for a small program but I would prefer using it in bigger projects over creating my own throttle function which I would have to test too :) – Divyanshu Maithani Jun 29 '18 at 06:05
  • prefer `lodash-es` over `lodash` for modern projects – dlq Nov 14 '21 at 04:01
12

I've just needed a throttle/debounce function for window resize event, and being curious, I also wanted to know what these are and how they work.

I've read multiple blog posts and QAs on SO, but they all seem to overcomplicate this, suggest libraries, or just provide descriptions and not simple plain JS implementations.

I won't provide a description since it's plentiful. So here's my implementation:

function throttle(callback, delay) {
    var timeoutHandler = null;
    return function () {
        if (timeoutHandler == null) {
            timeoutHandler = setTimeout(function () {
                callback();
                timeoutHandler = null;
            }, delay);
        }
    }
}

function debounce(callback, delay) {
    var timeoutHandler = null;
    return function () {
        clearTimeout(timeoutHandler);
        timeoutHandler = setTimeout(function () {
            callback();
        }, delay);
    }
}

These might need tweaks (e.g., initially the callback isn't called immediately).

See the difference in action (try resizing the window):

function throttle(callback, delay) {
    var timeoutHandler = null;
    return function () {
        if (timeoutHandler == null) {
            timeoutHandler = setTimeout(function () {
                callback();
                timeoutHandler = null;
            }, delay);
        }
    }
}

function debounce(callback, delay) {
    var timeoutHandler = null;
    return function () {
        clearTimeout(timeoutHandler);
        timeoutHandler = setTimeout(function () {
            callback();
        }, delay);
    }
}

var cellDefault  = document.querySelector("#cellDefault div");
var cellThrottle = document.querySelector("#cellThrottle div");
var cellDebounce = document.querySelector("#cellDebounce div");

window.addEventListener("resize", function () {
    var span = document.createElement("span");
    span.innerText = window.innerWidth;
    cellDefault.appendChild(span);
    cellDefault.scrollTop = cellDefault.scrollHeight;
});

window.addEventListener("resize", throttle(function () {
    var span = document.createElement("span");
    span.innerText = window.innerWidth;
    cellThrottle.appendChild(span);
    cellThrottle.scrollTop = cellThrottle.scrollHeight;
}, 500));

window.addEventListener("resize", debounce(function () {
    var span = document.createElement("span");
    span.innerText = window.innerWidth;
    cellDebounce.appendChild(span);
    cellDebounce.scrollTop = cellDebounce.scrollHeight;
}, 500));
table {
    border-collapse: collapse;
    margin: 10px;
}
table td {
    border: 1px solid silver;
    padding: 5px;
}
table tr:last-child td div {
    width: 60px;
    height: 200px;
    overflow: auto;
}
table tr:last-child td span {
    display: block;
}
<table>
    <tr>
        <td>default</td>
        <td>throttle</td>
        <td>debounce</td>
    </tr>
    <tr>
        <td id="cellDefault">
            <div></div>
        </td>
        <td id="cellThrottle">
            <div></div>
        </td>
        <td id="cellDebounce">
            <div></div>
        </td>
    </tr>
</table>

JSFiddle

akinuri
  • 10,690
  • 10
  • 65
  • 102
  • Poorly designed and induces a great delay on everything it is attached to which makes the website unresponsive to the user. – Jack G Aug 30 '18 at 10:50
  • 4
    @commonSenseCode What does "does not even work" even mean? I've provided demo code. It clearly works. Try to be more elaborate, please. Whatever is not working, I'm pretty sure it's got something to do with your implementation. – akinuri Sep 25 '18 at 09:10
  • The clearInterval() call in throttle() doesn't make sense, both because setTimeout() should be paired with clearTimeout() rather than clearInterval(), and because it's meaningless to cancel a timer after it has already fired. Also, I would expect the first callback to be instant, and subsequent callbacks to be delayed, but I guess delaying the first one too can be good or bad depending on what you want. – Bill Keese Apr 20 '21 at 15:46
  • @BillKeese Oh, you're right. There's no need to clear the timeout in the throttle. I suspect it's an artifact from copy/paste/edit of the debounce function. And as for the immediate call, that can be tweaked. I didn't want to complicate the functions with additional features. I'm a fan of the saying *"A picture is worth a thousand words"*, so I wanted to provide simple demos. – akinuri Apr 20 '21 at 17:44
10

Here's how I implemented throttle function in ES6 in 9LOC, hope it helps

function throttle(func, delay) {
  let timeout = null
  return function(...args) {
    if (!timeout) {
      timeout = setTimeout(() => {
        func.call(this, ...args)
        timeout = null
      }, delay)
    }
  }
}

Click on this link to see how it works.

vipin goyal
  • 671
  • 11
  • 17
  • 2
    Simple, but rather ineffective: it will delay the function even when not appropriate, and it will not keep the pending event fresh, resulting potentially major lag in the user interactions lag. Also, the use of the `...` spread syntax is inappropriate because only 1 argument is ever passed to the event listener: the event object. – Jack G Oct 20 '17 at 15:12
  • 6
    @JackGiffin : Using spread is not inappropriate; nothing limits the throttle function to only being used for an event handler. – Lawrence Dol Mar 06 '20 at 01:31
  • This will fire the `...args` of the initial call, rather the ones of the latest. – Izhaki Jul 07 '21 at 23:26
6

I've seen a lot of answers here that are way too complex for "a simple throttle in js".

Almost all of the simpler answers just ignore calls made "in throttle" instead of delaying execution to the next interval.

Here's a simple implementation that also handles calls "in throttle":

const throttle = (func, limit) => {
  let lastFunc;
  let lastRan = Date.now() - (limit + 1); //enforces a negative value on first run
  return function(...args) {
    const context = this;
    clearTimeout(lastFunc);
    lastFunc = setTimeout(() => {
      func.apply(context, args);
      lastRan = Date.now();
    }, limit - (Date.now() - lastRan)); //negative values execute immediately
  }
}

This is almost the exact same implementation for a simple debounce. It just adds a calculation for the timeout delay which requires tracking when the function was last ran. See below:

const debounce = (func, limit) => {
  let lastFunc;
  return function(...args) {
    const context = this;
    clearTimeout(lastFunc);
    lastFunc = setTimeout(() => {
      func.apply(context, args)
    }, limit); //no calc here, just use limit
  }
}
rsimp
  • 791
  • 9
  • 9
3

Simple solution in ES6. Codepen Demo

const handleOnClick = () => {
  console.log("hello")
}

const throttle = (func, delay) => {
  let timeout = null;

  return function (...args) {
    if (timeout === null) {
      func.apply(this, args);
      
      timeout = setTimeout(() => {
        timeout = null;
      }, delay)
    }
  }
}

document.querySelector("#button").addEventListener("click", throttle(handleOnClick, 500))
<button type="button" id="button">Click me</button>
vaskort
  • 2,591
  • 2
  • 20
  • 29
2

Here's my own version of Vikas post:

throttle: function (callback, limit, time) {
    var calledCount = 0;
    var timeout = null;

    return function () {
        if (limit > calledCount) {
            calledCount++;
            callback(); 
        }
        if (!timeout) {
            timeout = setTimeout(function () {
                calledCount = 0
                timeout = null;
            }, time);
        }
    };
}

I find that using setInterval is not a good idea.

makore
  • 47
  • 3
2

With leading and trailing invocations:

const throttle = (fn, ms) => {
  let locked = false

  return function () {
    if (!locked) {
      locked = true
      fn.apply(this, arguments)

      setTimeout(() => {
        fn.apply(this, arguments)
        locked = false
      }, ms)
    }
  }
}

Test case:

function log({ gender, address }) {
  console.log({
    name: this.name,
    gender,
    address,
  })
}

const jack = {
  name: 'Jack',
  log: throttle(log, 3000),
}

Array.from({ length: 5 }, () => jack.log({ gender: 'Male', address: 'LA' }))
Wenfang Du
  • 8,804
  • 9
  • 59
  • 90
1

I made a npm package with some throttling functions:

npm install function-throttler

throttleAndQueue

Returns a version of your function that can be called at most every W milliseconds, where W is wait. Calls to your func that happen more often than W get queued up to be called every W ms

throttledUpdate

Returns a version of your function that can be called at most every W milliseconds, where W is wait. for calls that happen more often than W the last call will be the one called (last takes precedence)

throttle

limits your function to be called at most every W milliseconds, where W is wait. Calls over W get dropped

Community
  • 1
  • 1
Almenon
  • 1,191
  • 11
  • 22
1

There is a library suited for this purpose, it's Backburner.js from Ember.

https://github.com/BackburnerJS/

You'd use it so.

var backburner = new Backburner(["task"]); //You need a name for your tasks

function saySomething(words) {
  backburner.throttle("task", console.log.bind(console, words)
  }, 1000);
}


function mainTask() {
  "This will be said with a throttle of 1 second per word!".split(' ').map(saySomething);
}

backburner.run(mainTask)
Rainb
  • 1,965
  • 11
  • 32
1

This is not a throttle function by definition but a debouncer. A callback function takes arguments (args), and still it works wrapped with the debouncer function. Be free to customize delay time according to your app needs. 1 time per 100ms is used for development mode, event "oninput" is just an example for the frequent case of its use:

const callback = (...args) => {
  console.count('callback throttled with arguments:', args);
};

const debouncer = (callback, limit) => {
  let timeoutHandler = 'null'

  return (...args) => {
    if (timeoutHandler === 'null') {
      timeoutHandler = setTimeout(() => {            
        callback(...args)
        timeoutHandler = 'null'
      }, limit)
    }
  }
}

window.addEventListener('oninput', debouncer(callback, 100));
Roman
  • 19,236
  • 15
  • 93
  • 97
  • It also waits 1000 milliseconds before it calls the callback which is not good at all. Users want a responsive page, not a sluggish nightmare. – Jack G Oct 12 '18 at 13:54
  • Thank you for your comment. You are wellcome to customize the callback time according to the requirements of your application. Usually it is between 100 and 500 milliseconds. This 1000 milliseconds allows you to check and debug the function. – Roman Oct 12 '18 at 19:19
  • @JackGiffin In some scenarios, the leading-edge invocation is not wanted. An example would be an auto-save. – pettys Nov 07 '18 at 17:18
  • @pettys If there is no leading-edge invocation, then it is not a throttle function. Rather, it is a debounce function. – Jack G Nov 08 '18 at 21:51
  • 6
    @JackGiffin I don't think that's the correct distinction between throttle and debounce. I believe throttle is, "Invoke no more then x times/sec," whereas debounce is "For sequences of source events where the gap is less than x sec, treat the whole sequence as a single instance." Subtle difference, but well-illustrated here: http://demo.nimius.net/debounce_throttle/ It seems to me both throttle and debounce have meaningful and useful no-leading-edge configurations. – pettys Nov 09 '18 at 16:08
1

In below example, try clicking the button multiple times, but the myFunc function would be executed only once in 3 sec. The function throttle is passed with the function to be executed and the delay.It returns a closure, which is stored in obj.throttleFunc. Now since obj.throttleFunc stores a closure, the value of isRunning is maintained inside it.

function throttle(func, delay) {
  let isRunning;
  return function(...args) {
    let context = this;        // store the context of the object that owns this function
    if(!isRunning) {
      isRunning = true;
      func.apply(context,args) // execute the function with the context of the object that owns it
      setTimeout(function() {
        isRunning = false;
      }, delay);
    }
  }
}

function myFunc(param) {
  console.log(`Called ${this.name} at ${param}th second`);
}

let obj = {
  name: "THROTTLED FUNCTION ",
  throttleFunc: throttle(myFunc, 3000)
}

function handleClick() {
  obj.throttleFunc(new Date().getSeconds());
}
button {
  width: 100px;
  height: 50px;
  font-size: 20px;
}
    <button onclick="handleClick()">Click me</button>

If we don't want the context or arguments to be passed, then a simpler version of this would be as following:

function throttle(func, delay) {
  let isRunning;
  return function() {
    if(!isRunning) {
      isRunning = true;
      func()
      setTimeout(function() {
        isRunning = false;
      }, delay);
    }
  }
}

function myFunc() {
  console.log('Called');
}


let throttleFunc = throttle(myFunc, 3000);

function handleClick() {
  throttleFunc();
}
button {
  width: 100px;
  height: 50px;
  font-size: 20px;
}
<button onclick="handleClick()">Click me</button>
Ayan
  • 8,192
  • 4
  • 46
  • 51
1

I also want to suggest a simple solution for when there is only 1 function you know you will call (for example: Search)

here is what i did in my project

let throttle;

function search() {
    if (throttle) {
      clearTimeout(throttle);
    }
    throttle = setTimeout(() => {
      sendSearchReq(str)
    }, 500);
  }

Search is called on input change event

Dan Levin
  • 718
  • 7
  • 16
  • 1
    This is not exactly a throttle function. Every call to the `search()` function will reset the timeout. So if I were to call the `search()` function above every millisecond, it will only execute the `sendSearchReq` once and then never again, instead of every 500 ms. This function is more of a delay, than a throttle. – tillsanders Jul 05 '20 at 16:39
  • 1
    This is debounce not throttle – Eduard Jacko Nov 07 '20 at 15:59
1
function throttle(targetFunc, delay){
  let lastFunc;
  let lastTime;

  return function(){
    const _this = this;
    const args = arguments;

    if(!lastTime){
      targetFunc.apply(_this, args);
      lastTime = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(function(){
        targetFunc.apply(_this, args);
        lastTime = Date.now();
      }, delay - (Date.now() - lastTime));
    }
  }
}

Try it :

window.addEventListener('resize', throttle(function() {
  console.log('resize!!');
}, 200));
BeingSuman
  • 3,015
  • 7
  • 30
  • 48
  • 1
    This is the best simple implementation that I've seen but you can actually make is slightly simpler, will add new answer below – rsimp Jan 07 '21 at 21:57
1

CodeSandbox

const { now } = Date;

export default function throttle(func, frameDuration) {
  let timeout = null;
  let latest;
  const epoch = now();

  function getDurationToNextFrame() {
    const elapsed = now() - epoch;
    const durationSinceLastFrame = elapsed % frameDuration;
    return frameDuration - durationSinceLastFrame;
  }

  function throttled(...args) {
    latest = () => {
      func.apply(this, args);
    };
    if (!timeout) {
      timeout = setTimeout(() => {
        latest();
        timeout = null;
      }, getDurationToNextFrame());
    }
  }

  return throttled;
}
Izhaki
  • 23,372
  • 9
  • 69
  • 107
0

Simple throttle function -

Note- Keep on clicking on the button , You'll see console log at first on click and then only after every 5 seconds until you're keep clicking.

HTML -

<button id='myid'>Click me</button>

Javascript -

const throttle = (fn, delay) => {
  let lastTime = 0;
  return (...args) => {
      const currentTime = new Date().getTime();
      if((currentTime - lastTime) < delay) {
        return;
      };
      lastTime = currentTime;
      return fn(...args);
  }
};

document.getElementById('myid').addEventListener('click', throttle((e) => {
  console.log('I am clicked');
}, 5000));
Avadhut Thorat
  • 997
  • 11
  • 7
0

We can also implement using a flag-

var expensive = function(){
    console.log("expensive functionnns");
}

window.addEventListener("resize", throttle(expensive, 500))

function throttle(expensiveFun, limit){
    let flag = true;
    return function(){
        let context = this;
        let args = arguments;
        if(flag){
            expensiveFun.apply(context, args);
            flag = false;
            setTimeout(function(){
                flag = true;
            }, limit);
        }
    }
}
ganesh phirke
  • 471
  • 1
  • 3
  • 12
0

Here is a bit modernized and simplified version of @clément-prévost answer

function throttle(func, wait, options = {}) {
  let timeout = null;
  let previous = 0;

  const later = (...args) => {
    previous = options.leading === false ? 0 : Date.now();
    func(...args);
  };

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

    if (!previous && options.leading === false) {
      previous = now;
    }

    const remaining = wait - (now - previous);

    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      func(...args);
    } else if (options.trailing !== false) {
      clearTimeout(timeout);
      timeout = setTimeout(() => later(...args), remaining);
    }
  };
}

function myFunc(a) {
  console.log(`Log: ${a} ${this.val}`);
}

const myFuncThrottled = throttle(myFunc.bind({val: 42}), 1234, {leading: true, trailing: true})

myFuncThrottled(1)
myFuncThrottled(2)
myFuncThrottled(3)
Serhii Holinei
  • 5,758
  • 2
  • 32
  • 46
0
function throttle(CB,ms=300,Id='Identifier for the callback(CB)'){
  Id = Id || ""+CB
  var N = throttle.N = throttle.N || {};  // Static variable N to store all callbacks ids and their status 
  if( N[Id] ) return;             // already in the queue to run 
  N[Id] = 1;                      // add it the queue 
  setTimeout(()=>{
    N[Id] = 0;                    // remove it from the queue
    CB();                         // finally call the function 
  }, ms);
}



for(var i=0;i<100;i++){
   throttle(e=>console.log("Hi1"),1e3,'F1');
}

// will only  output : Hi1
// this function guarantee the callback to run at least once 
Saif
  • 29
  • 1
  • 5
0

Some great solutions here already, but I was looking for a modern version with trailing (and optionally leading) executions, with the last passed arguments provided to each function call:

const throttle = (fn, wait=500, leading=true) => {
  let prev, timeout, lastargs;
  return (...args) => {
    lastargs = args;
    if (timeout) return;
    timeout = setTimeout(() => {
      timeout = null;
      prev = Date.now();
      // let's do this ... we'll release the stored args as we pass them through
      fn.apply(this, lastargs.splice(0, lastargs.length));
      // some fancy timing logic to allow leading / sub-offset waiting periods
    }, leading ? prev && Math.max(0, wait - Date.now() + prev) || 0 : wait);
  };
}

Usage:

x = throttle((...args) => console.log(...args));
let n = 0;
x(++n, 'boom');
x(++n, 'boom');
x(++n, 'boom');
som
  • 2,023
  • 30
  • 37
0

if there will be more than one function defining them one by one would not be maintainable so i would suggest use a helper class to keep values for each

class slowDown {
    constructor(cb,timeGap){
        this.last = 0
        this.run = function(){
            let current = Date.now(),
                shouldRun = (current - this.last) >= timeGap
            if(shouldRun){
                cb(current - this.last)
                this.last = current
            }            
        }
    }
}

// example use
const press = new slowDown(timeElapsed => {
    // define function here which you wanted to slow down
    console.log("pressed after " + timeElapsed + " ms")
},750)

window.addEventListener("keydown",()=>{
    press.run()
})
Amir Rahman
  • 1,076
  • 1
  • 6
  • 15
-1

Below is the simplest throttle I could think of, in 13 LOC. It creates a timeout each time the function is called and cancels the old one. The original function is called with the proper context and arguments, as expected.

function throttle(fn, delay) {
  var timeout = null;

  return function throttledFn() {
    window.clearTimeout(timeout);
    var ctx = this;
    var args = Array.prototype.slice.call(arguments);

    timeout = window.setTimeout(function callThrottledFn() {
      fn.apply(ctx, args);
    }, delay);
  }
}

// try it out!
window.addEventListener('resize', throttle(function() {
  console.log('resize!!');
}, 200));
alessioalex
  • 62,577
  • 16
  • 155
  • 122
  • 14
    This is a debounce, not a throttle. If i call this 100 in 1000ms, it will fire 1 time after 1200ms. A throttled function should fire 5 times – Dogoku Jan 20 '17 at 07:24