2
\$\begingroup\$

I'm calling an expensive function that dynamical changes as the end user types in a textbox. To reduce the amount of calls to the function I've used setTimeout to delay calling the function, and if the function is called again whilst waiting to run, it'll also remove the last function request via clearTimeout.

The function for this is:

// To not have different functions conflict
function makeDelay() {
    var timerID = false;
    return function (fn, timeout) {
        if (timerID !== false) {
            clearTimeout(timerID);
        }
        if (timeout == undefined) {
            timeout = 500;
        }
        timerID = setTimeout(function() {
            timerID = false;
            fn();
        }, timeout);
    }
}
var delay = makeDelay();
var $peoplePicker = $('.ms-peoplePicker');
var $results = $peoplePicker.find(".ms-PeoplePicker-results");

However I don't know what the most idiomatic way to use this function is. I know that I can use one of the following:

  • A closure

    function get(input, html_output) {
        return function () {
            if (!input) {
                return;
            }
            $.ajax('/_api/pp_search/' + input).done(function (data) {
                html_output[0].innerHTML = data;
            })
        }
    }
    
    $peoplePicker.on('input', '.ms-PeoplePicker-searchField', function(event) {
        delay(get(event.currentTarget.value, $results));
    });
    
  • bind

    function get(input, html_output) {
        if (!input) {
            return;
        }
        $.ajax('/_api/pp_search/' + input).done(function (data) {
            html_output[0].innerHTML = data;
        })
    }
    
    $peoplePicker.on('input', '.ms-PeoplePicker-searchField', function(event) {
        delay(get.bind(undefined, event.currentTarget.value, $results));
    });
    
  • Anonymous functions

    function get(input, html_output) {
        if (!input) {
            return;
        }
        $.ajax('/_api/pp_search/' + input).done(function (data) {
            html_output[0].innerHTML = data;
        })
    }
    
    $peoplePicker.on('input', '.ms-PeoplePicker-searchField', function(event) {
        delay(function () {
            get(event.currentTarget.value, $results);
        });
    });
    

Using a closure seems like the nicest, as you don't have to explicitly make a delayed function at usage via bind or an anonymous function. However, anonymous functions seem to be the de facto way to call functions like these.

Which, if any, way should I use this function?


Notes

I ask because I made a JavaScript program that heavily used closures for this style of function, but was really hard to read.

\$\endgroup\$
0

1 Answer 1

2
\$\begingroup\$

Actually, what you are looking for is already done in Lodash via debounce or throttle. And yes, a closure is probably the best way to do this. So let's take your closure example and improve it.

Instead of returning a function, why not just define the function normally. That way, there's no developer overhead. Devs shouldn't know about your limiter function just to make their APIs.

// We'll be fine with this
function get(input, html_output) {
  if (!input) return;    

  $.ajax('/_api/pp_search/' + input).done(function (data) {
    html_output[0].innerHTML = data;
  });

}

delay is a bit deceiving as the word "delay" can mean a lot of things. It can mean "pause", which then equates to sleep which does an entirely different thing. "debounce" and "throttle" are actually better names. Verbose names aren't that bad as well (except for the extra keystrokes). Something like holdExecutionUntilAfter.

Now for the implementation, let's accept a delay and the function we want to delay. Our return will be our "proxy" function that controls the real function from executing.

function holdExecutionUntilAfter(fn, delay){
  var timer = null;

  return function(){
    if(timer) clearTimeout(timer);

    setTimeout(function(){
      fn.apply(null, arguments);
      clearTimeout(timer);
    }, delay);
  }
}

As simple as that! What it does is get a reference of the real function into a closure. Then we return a "proxy" function which well be using to call the real function. When it gets called, it resets the timer and kicks off another. We use apply to pass in the arguments like you normally would in the regular call. Usage will be as simple as:

var get = delay(function(input, html_output){
  if (!input) return;    
  $.ajax('/_api/pp_search/' + input).done(function (data) {
    html_output[0].innerHTML = data;
  });
}, 500);

get(event.currentTarget.value, $results);

// or verbosely

function get(input, html_output){
  if (!input) return;    

  $.ajax('/_api/pp_search/' + input).done(function (data) {
    html_output[0].innerHTML = data;
  });
}
var proxiedGet = delay(get, 500);

proxiedGet(event.currentTarget.value, $results);
\$\endgroup\$
1
  • \$\begingroup\$ Thank you for the review, it's a lovely solution which reminds me of Python's @ sugar. \$\endgroup\$
    – Peilonrayz
    Feb 16, 2016 at 20:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.