45

I have a keyup event bound to a function that takes about a quarter of a second to complete.

$("#search").keyup(function() {
  //code that takes a little bit to complete
});

When a user types an entire word, or otherwise presses keys rapidly, the function will be called several times in succession and it will take a while for them all to complete.

Is there a way to throttle the event calls so that if there are several in rapid succession, it only triggers the one that was most recently called?

Peter Olson
  • 139,199
  • 49
  • 202
  • 242
  • 2
    This is a super useful advanced JS topic, more people should see it. – th3byrdm4n Feb 07 '14 at 22:39
  • 1
    There's debate about whether a throttle or debouncer is actually what you want. Refer to this: http://stackoverflow.com/a/40268544/1450294 – Michael Scheper Oct 26 '16 at 17:26
  • similar question with a better answer than those below https://stackoverflow.com/questions/9424752/jquery-change-with-delay – dangel Sep 15 '19 at 00:40

7 Answers7

69

Take a look at jQuery Debounce.

$('#search').keyup($.debounce(function() {
    // Will only execute 300ms after the last keypress.
}, 300));
josh3736
  • 139,160
  • 33
  • 216
  • 263
  • 1
    this does not work when i use multiple selector like `.sel1, .sel2 sel3` – HungryCoder Sep 25 '12 at 15:55
  • 2
    @HungryCoder: [It does work.](http://jsfiddle.net/josh3736/Xnnmf/) Of course, every matching element will share the same timer, so the callback won't fire until after you completely stop sending events. – josh3736 Sep 25 '12 at 16:36
  • 3
    Debounce isn't throttling..it's something else – vsync Mar 17 '14 at 20:18
  • @vsync: Nitpicking terminology. OP said "throttle" but really meant "debounce" – josh3736 Mar 17 '14 at 20:22
  • @josh3736 - in that case you should edit his post and title, so people who are coming here won't get confused. – vsync Mar 17 '14 at 20:56
  • 8
    This does not answer the question. The OP may think it has been answered, but this is not even halfway toward what is described, and in no way approximates expected behaviour for an autocomplete result. Debounce is the wrong function. OP is actually asking for throttle. See: https://css-tricks.com/the-difference-between-throttling-and-debouncing/ – Aeschylus Jul 13 '15 at 00:53
  • this answer is outdated; debounce has apparently been removed: http://api.jquery.com/?s=debounce – chharvey Oct 20 '17 at 20:11
  • It's a plugin, not a core feature. – josh3736 Oct 20 '17 at 21:17
  • @Aeschylus This is a good point you highlighted. even though in some cases you can use it without worrying too much about the terminology as it would simply do what you need there are cases I have personal experiences where mixing these too concepts became a big issue. sometimes you really need throttling instead of waiting n seconds to function to execute. +10 for the mention ! – HarshaXsoad Jan 31 '20 at 09:37
  • This answer should show up on top! – Simon Ferndriger Aug 15 '22 at 14:44
13

Here is a potential solution that doesn't need a plugin. Use a boolean to decide whether to do the keyup callback, or skip over it.

var doingKeyup = false;

$('input').keyup(function(){
    if(!doingKeyup){
        doingKeyup=true;
        // slow process happens here
        doingKeyup=false;
    }
});
Nathan Manousos
  • 13,328
  • 2
  • 27
  • 37
  • 2
    This is a throttle, not a debounce. In the case of a search field, you want to run the search *after* the user has finished typing (a debounce), not after they've typed one character (a throttle). The Debounce plugin is really just a <1 kB convenience method that wraps calls to `setTimeout`/`clearTimeout`. – josh3736 Sep 10 '11 at 17:19
  • 1
    I'm not making assumptions about how his search field is intended to work. – Nathan Manousos Sep 10 '11 at 17:35
  • 1
    @josh3736 To be fair, the question title did ask for a "throttle". I hadn't heard of "debounce", before, though. – Peter Olson Sep 11 '11 at 01:53
  • 1
    There is one other problem with this approach -- it won't fire on the final text string if last character or a series of characters were entered while the previous handler was running. – Michael Teper Aug 19 '12 at 21:34
  • 2
    Only if `// slow process` happens in another event loop, this would be right way otherwise since JS is single-threaded this is not useful. – Ashish Negi Feb 17 '15 at 10:45
  • As Ashish has pointed out, this is simply wrong, and is based on a deep ignorance of how the language and browser environment works. See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop. Also, I will echo what Peter Olson said above, the question calls for a throttle, not a debounce. To josh3736: as a user, why would I want results after I'm done typing? The whole point of bothering with keypresses is to get results _while I am typing_. Throttling simply prevents the function from being sent too often for the server to deal with it. – Aeschylus Jul 13 '15 at 00:33
  • See Chris Coyer on the difference between debounce and throttle: https://css-tricks.com/the-difference-between-throttling-and-debouncing/ – Aeschylus Jul 13 '15 at 00:39
6

You could also use the excellent Underscore/_ library.

Comments in Josh's answer, currently the most popular, debate whether you should really throttle the calls, or if a debouncer is what you want. The difference is a bit subtle, but Underscore has both: _.debounce(function, wait, [immediate]) and _.throttle(function, wait, [options]).

If you're not already using Underscore, check it out. It can make your JavaScript much cleaner, and is lightweight enough to give most library haters pause.

Community
  • 1
  • 1
Michael Scheper
  • 6,514
  • 7
  • 63
  • 76
2

Here's a clean way of doing it with JQuery.

    /* delayed onchange while typing jquery for text boxes widget
    usage:
        $("#SearchCriteria").delayedChange(function () {
            DoMyAjaxSearch();
        });

    */
    (function ($) {
        $.fn.delayedChange = function (options) {
            var timer;
            var o;

            if (jQuery.isFunction(options)) {
                o = { onChange: options };
            }
            else
                o = options;

            o = $.extend({}, $.fn.delayedChange.defaultOptions, o);

            return this.each(function () {
                var element = $(this);
                element.keyup(function () {
                    clearTimeout(timer);
                    timer = setTimeout(function () {
                        var newVal = element.val();
                        newVal = $.trim(newVal);
                        if (element.delayedChange.oldVal != newVal) {
                            element.delayedChange.oldVal = newVal;
                            o.onChange.call(this);
                        }

                    }, o.delay);
                });
            });


        };

        $.fn.delayedChange.defaultOptions = {
            delay: 1000,
            onChange: function () { }
        }

        $.fn.delayedChange.oldVal = "";


    })(jQuery);
AntonK
  • 2,303
  • 1
  • 20
  • 26
1

Two small generic implementations of throttling approaches. (I prefer to do it through these simple functions rather than adding another jquery plugin)

  1. Waits some time after last call

    This one is useful when we don't want to call for example search function when user keeps typing the query

function throttle(time, func) {
  if (!time || typeof time !== "number" || time < 0) {
      return func;
  }

  var throttleTimer = 0;

  return function() {
    var args = arguments;
    clearTimeout(throttleTimer);
    throttleTimer = setTimeout(function() {
      func.apply(null, args);
    }, time);
  }
}
  1. Calls given function not more often than given amount of time

    The following one is useful for flushing logs

function throttleInterval(time, func) {
  if (!time || typeof time !== "number" || time < 0) {
      return func;
  }

  var throttleTimer = null;
  var lastState = null;
  var eventCounter = 0;
  var args = [];

  return function() {
    args = arguments;
    eventCounter++;
    if (!throttleTimer) {
      throttleTimer = setInterval(function() {
        if (eventCounter == lastState) {
          clearInterval(throttleTimer);
          throttleTimer = null;
          return;
        }

        lastState = eventCounter;
        func.apply(null, args);
      }, time);
    }
  }
}

Usage is very simple:

The following one is waiting 2s after the last keystroke in the inputBox and then calls function which should be throttled.

$("#inputBox").on("input", throttle(2000, function(evt) {
  myFunctionToThrottle(evt);
}));

Here is an example where you can test both: click (CodePen)

Max
  • 381
  • 3
  • 6
-1

I came across this question reviewing changes to . They've added their own method for debounce and throttling. It looks like it might be the same as the jquery-debounce @josh3736 mentioned in his answer.

From their website:

// Debounced button click handler
$('.button').on('click', Foundation.utils.debounce(function(e){
  // Handle Click
}, 300, true));

// Throttled resize function
$(document).on('resize', Foundation.utils.throttle(function(e){
  // Do responsive stuff
}, 300));
th3byrdm4n
  • 172
  • 1
  • 10
-4

Something like this seems simplest (no external libraries) for a quick solution (note coffeescript):

running = false
$(document).on 'keyup', '.some-class', (e) ->
  return if running
  running = true
  $.ajax
    type: 'POST',
    url: $(this).data('url'),
    data: $(this).parents('form').serialize(),
    dataType: 'script',
    success: (data) ->
      running = false
Brian Armstrong
  • 19,707
  • 17
  • 115
  • 144