At times when dealing with functions that will be bound to an element you may need to prevent that function from being run multiple times. In order to do that you could use a JavaScript library such as jQuery and bind the function with the jQuery#one function. If that is the only other functionality you actually need though without adding a new library, you could using a function such as the following:

/**
 * Creates a new function which will call the original function only a limited
 * amount of times (defaulting to one time).
 * @param {!Function} fn  The function which should only be called a limited
 *     amount of times.
 * @param {number=} maxCalls  Optional.  Defaults to 1.  The maximum amount of
 *     times the specified function can be called.
 * @param {boolean=} throwErrors  Optional.  Defaults to false.  Specify true if
 *     once the limit is hit an error should be thrown instead of returned.
 * @return {!Function}  Returns a new wrapper function which will only call the
 *     specified function as many times as specified.  The data bound and passed
 *     into this function will be bound and passed onto the original function.
 *     After the limit has been reached an Error object will be returned with a
 *     message of "limit exceeded".
 */
function limit(fn, maxCalls, throwErrors) {
  maxCalls = maxCalls || 1;
  return function() {
    if(maxCalls-- > 0) {
      return fn.apply(this, arguments);
    }
    else {
      var err = new Error('limit exceeded');
      if(throwErrors) {
        throw err;
      }
      return err;
    }
  };
}

Example

Now that we have the definition for this limit function we can learn how to use it by example:

// Encapsulate data so the user cant cheat.
(function() {
  // Function to greet the user.
  var sayHello = limit(function(name) {
    alert('Hello ' + name + '!!!');
  });

  // Ask for the user's name.
  var userName = prompt('What is your name?', 'user');

  // Greet the user.
  sayHello(userName);

  // Try greeting the user again... this should fail.
  sayHello(userName);

  // Generate a random number from 1 to 10.
  var randomNumber = parseInt(Math.random() * 10) + 1;
  
  // A function which will only allow the user to make 3 guesses.  After
  // 3 guesses, subsequent calls will cause an error to be thrown.
  var makeGuess = limit(function(previousGuess) {
    return prompt('Guess a number between 1 and 10:', previousGuess);
  }, 3, true);
  
  // Play the guessing game.
  var guess = '';
  try {
    while(+(guess = makeGuess(guess)) != randomNumber) {
      if(guess < randomNumber) {
        alert('Nope, it is higher than that.');
      }
      else {
        alert('Nope, it is lower than that.');
      }
    }
    alert('You got it!!!');
  }
  catch(e) {
    alert('Unfortunately you hit your limit.\n'
      + 'The number I was thinking of was ' + randomNumber + '.');
  }
})();

You can click here to see the result of running the two preceeding blocks of code. Note that the greeting only appears once, even though it is in the code twice. Also notice that the try-catch clause is used to determine when the user tries to make another guess but the maximum amount of guesses have already been made.

Function Description

This limit function creates a new copy of the function passed in which can only be called a limited amount of times.

Parameters

  1. fn {Function}:
    The function which will only be called a limited amount of times.
  2. maxCalls {number}:
    Optional. Defaults to 1. The maximum amount of times the returned wrapper function can be called before producing an error.
  3. throwError {boolean}:
    Optional. Defaults to false. If true is specified an error will be thrown if the amount of calls is exceeded. Otherwise the error will simply be returned.

Returns

Returns a new wrapper function so that the context and the parameters passed to this wrapper function will be passed to fn iff maxCalls hasn’t been exceeded.

Final Notes

Personally I would think this is most useful for event handling but if you have anymore uses for it please let us know. 8)


Leave a Reply

Your email address will not be published. Required fields are marked *