Debouncing hecho fácil

Yamete frontend-kun!!!! >.<

¿Alguna vez les ha pasado que están escuchando un evento y tienen que hacer que reaccione algo a los cambios, pero dicho evento dispara varias decenas de acciones por segundo (o cientos o miles!!!!)?

Peor aún, si tienes que mandar dichos cambios por AJAX al backend, te topas con que saturas el servidor, te truena las conecciones o simplemente te banean por intento de DDoS.

mi vendors.js pesa 10mb!!!!

Entonces lo que tu necesitas es un debouncer.

Sacado de la documentación de VueJs, justamente tienen este problema y lo resuelven con _.debounce que viene en lodash. Y no me entiendan mal, si ya estás usando lodash, adelante. Pero si vas a cargar toda una librería por solo un elemento, mejor intenta resolverlo tu mismo, si no te vas a enfrentar al clásico "mi vendors.js pesa 10mb!!!!" sin contar que le metes carga excesiva a tu webpack cada que guardas… como sea, aqui mi solución:

class Debouncer {
  constructor (fn, timeout) {
    this.fn = fn
    this.timeout = timeout
    this.pile = []
    this.working = false
  }
  execute () {
    if (this.working) return
    this.working = true
    this.pile = []
    this.fn(() => {
      setTimeout(() => {
        this.working = false
        this.tryExecute()
      }, this.timeout)
    })
  }
  tryExecute () {
    if (this.pile.length === 0) return
    this.execute()
  }
  touch () {
    this.pile.push(true)
    this.tryExecute()
  }
}

Y la forma de utilizarlo es:

const debouncer = new Debouncer(((cb) => {
  // aquí tu llamada de AYAX
  // en el callback, solo llama "cb"
  $.post('endpoint', payload, cb)
}), 1000) // este parámetro es la cantidad de milisegundos para debouncear

Obvio si usas promesas, solo cambia los callbacks por promesas.

Aquí un ejemplo funcionando:
https://runkit.com/zerodragon/debouncer-example

Como pueden ver, se mandó a llamar la función 10 veces, pero solo se ejecutó dos (una al inicio y otra por todas las acumuladas al pasar los 1000 milisegundos de espera)

Zero Out