Throttling y debounce en JavaScript

Estas técnicas (throttling y debounce), permiten limitar programaticamente la frecuencia y veces que se ejecuta una función.

Esto es de enorme importancia, ya que por lo general, el uso que tiene JavaScript en las aplicaciones web tradicionales implica trabajar con un paradigma event-driven, en el cual es la interacción del usuario la cual va determinando el flujo de ejecución de las diversas partes de una aplicación.
En este contexto la importancia de estas técnicas esta dada por su capacidad de regular el constante input del usuario a (como máximo) el limite deseado.

Un ejemplo común de esta situación es la validación de un campo mediante algún evento repetido o las búsquedas autocompletadas, casos en los cual es deseable que el usuario haya detenido el input para no generar trabajo innecesario.

Disclaimer: En la web suelen ser bastante ambigüas las denominaciones de estas técnicas, siendo que según otro desarrollador/autor pueden corresponder los nombres con el uso aquí referido, estar invertidas o inclusive englobar ambas bajo un mismo nombre.

Si bien ambas técnicas permiten limitar la frecuencia de ejecución de una función dada, la finalidad de ambas difiere levemente:

Throttling

Esta técnica consiste en limitar un input reiterado a una frecuencia máxima, esto es, que la función en cuestión no se ejecutará más de una vez en la duración del período definido.
Si aplicamos un throttling de un segundo a un evento keyup de un textarea,el mismo se ejecutará como máximo, una única vez por segundo. Todas las ejecuciones que se sucedan hasta alcanzar el intervalo de ejecución deseado serán descartadas.

Graficamente:

throttle

Ejecución cada un intervalo dado

Debounce

Esta técnica consiste en limitar un input reiterado a una única ejecución que se dará una vez que se alcance un tiempo de espera deseado. El tiempo de espera se verá reseteado en cada llamada a la función sobre la cual se aplique el debounce.
Si aplicamos un debounce de un segundo a un evento keyup de un textarea, el mismo se ejecutará cuando pase un segundo de la última tecla. Todas las ejecuciones serán descartadas hasta alcanzar el intervalo ocioso, momento en el cual se ejecutará la función .

Graficamente:

debounce

Ejecución una vez alcanzado un tiempo ocioso

Implementación

Atención: En las referencias dadas (Leer más), se pueden encontrar otras variantes de estas técnicas, de igual manera en otros sitios no mencionados en esta entrada.

Throttling

Lla implementación es bastante sencilla, per aún así tiene dos variantes. La primera es almacenar la referencia del tiempo transcurrido como una variable dentro de un clousure, y la segunda como propiedad de la función en sí.
En ambos casos la funcionalidad es similar.

Primer caso, utilizando un clousure:

/**
 * Limita la cantidad de ejecuciones de
 * de la funcion a una cada {delay} ms
 *
 * @param {Function} fn
 * @param {Numbre} delay
 * @param {Object} scope
 * @return {Function}
 */
function throttle(fn, delay, scope) {
	// Esta variable existe dentro del
	// clousure
	var lastExecution = 0;

	return function() {
		var d = +new Date;

		// Si la ultima ejecucion fue hace menos
		// que el tiempo establecido...
		if(d - lastExecution < delay ) {
			return;
		}

		lastExecution = d;

		fn.apply(scope, arguments);
	}
}

Segundo caso, utilizando una propiedad de la función:

/**
 * Limita la cantidad de ejecuciones de
 * de la funcion a una cada {delay} ms
 *
 * @param {Function} fn
 * @param {Numbre} delay
 * @param {Object} scope
 * @return {Function}
 */
function throttle(fn, delay, scope) {
	// Esta property existe dentro de
	// la function fn
	fn.lastExecution = 0;

	return function() {
		var d = +new Date;

		// Si la ultima ejecucion fue hace menos
		// que el tiempo establecido...
		if(d - fn.lastExecution < delay ) {
			return;
		}

		fn.lastExecution = d;

		fn.apply(scope, arguments);
	}
}

La diferencia entre las dos formas radica en la visibilidad de la propiedad que almacena el tiempo de la última ejecución, siendo visible si es una property de la función.

Debounce

Al igual que en el caso del throttle, se puede almacenar la referencia de la última solicitud de ejecución en una variable de clousure o en una propiedad de la función. En este caso voy a ejemplificar solo la primer variante:

/**
 * Limita la cantidad de ejecuciones de
 * de la funcion a un tiempo ocioso dado
 *
 * @param {Function} fn
 * @param {Numbre} delay
 * @param {Object} scope
 * @return {Function}
 */
function debounce(fn, delay, scope) {
	// Esta variable existe dentro del
	// clousure
	var lastExecution;

	return function() {
		var args = arguments;

		// Eliminamos la ultima ejecución pendiente
		clearTimeout(lastExecution);

		// Definimos una nueva ejecución luego del
		// tiempo ocioso definido
		lastExecution = setTimeout(function() {
			fn.apply(scope, args);
		}, delay);
	}
}

Casos de uso

Por lo general, ambas técnicas pueden usarse con igual éxito en escenarios similares. De igual manera, hay que considerar el contexto en el cual se diferencian:

En las situaciones en la que el usuario genera inputs hasta un momento dado (en criollo, se decide) se debería utilizar un debounce:

  • El usuario recorre campos mediante tabs, cuyo focus dispara un evento que trae los datos. Lo ideal es esperar a que deje de recorrer los campos a fin de evitar traer datos que no se requieren.
  • El usuario completa un campo con validación contra el servidor. Lo ideal es esperara que deje de tipear un tiempo dado antes de validarlo.
// Creamos un listener pero limitamos su ejecucion
// a una vez cada 500 ms
$('#myElement').click(throttle(myHandler, 500));

En las situaciones en la que el usuario recibe un feedback (operación costosa) por cada input o acción que genera  son las aquellas candidatas a utilizar throttling.

  • El usuario recorre un paginado Ajax, por lo cual limitamos la velocidad en la cual puede recorrerlo a un número razonable.
  • En un sitio con carga dinámica de contenido (se cargan elementos al scrollear), reducimos la frecuencia con la cual se solicita el contenido en cuestión (operación costosa).
// Creamos un listener pero limitamos su ejecucion
// a 500 ms ociosos
$('#myElement').scroll(debounce(myHandler, 500));

Leer más:

 


Shortlink: http://goo.gl/SRZaY




Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Comment

You may use these tags : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>