Underscore: Sugerencia: _.debounce y _.throttle toman un parámetro adicional sobre cómo combinar argumentos

Creado en 22 sept. 2011  ·  11Comentarios  ·  Fuente: jashkenas/underscore

Si uso _.debounce() para hacer una función de rebote y luego la llamo 3 veces seguidas con 3 conjuntos diferentes de argumentos, entonces (a partir de v1.1.7) la función de carga útil envuelta finalmente se llamará con los argumentos especificados por el Tercera llamada: es decir, se descartan el primer y el segundo argumento.

Si bien esto suele ser válido (y lo que suele hacer la eliminación de rebotes de teclas, por lo tanto, es un valor predeterminado razonable), me encuentro queriendo usar la eliminación de rebotes para acumular argumentos, por ejemplo, tengo una llamada AJAX que puede obtener varias claves a la vez, así que uso la eliminación de rebotes para almacenar en búfer. presione las teclas por un segundo y luego emita una solicitud combinada.

Por lo tanto, mi sugerencia es que debounce tome un tercer argumento "combinado" opcional que se llamará con 2 argumentos,

  • el primero es la lista de argumentos acumulados hasta el momento (posiblemente indefinido, es decir, no hay lista en la primera llamada)
  • el segundo es la lista de argumentos para la última llamada (posiblemente una lista vacía)
    y devuelve la nueva lista de argumentos acumulados. Cuando se llama a la función de carga útil, se borra la lista acumulada de argumentos.

Si no se pasa ningún valor para el parámetro de combinación, la combinación predeterminada conserva el comportamiento existente
function(acc, newargs) { return newargs; }
pero también puede decidir utilizar el primer conjunto de argumentos
function(acc, newargs) { return acc || newargs; }
o lo que quiero hacer, que es simplemente agregar todos los argumentos
function(acc,newargs) { return (acc || []).concat(newargs); }
y, por supuesto, otros pueden querer hacer algo más elegante

Esto requeriría el siguiente cambio en la función de límite interno

  // Internal function used to implement `_.throttle` and `_.debounce`.
  var limit = function(func, wait, debounce, combine) {
    var timeout, allargs;
    return function() {
      var context = this;
      allargs = combine(allargs,  slice.call(arguments,0))
      var throttler = function() {
        timeout = null;
        var args = allargs;
        allargs = undefined;
        func.apply(context, args);
      };
      if (debounce) clearTimeout(timeout);
      if (debounce || !timeout) timeout = setTimeout(throttler, wait);
    };
  };

y luego un cambio para debounce para aceptar y pasar el nuevo argumento con el valor predeterminado si no se especifica.

  _.debounce = function(func, wait, combine) {
    return limit(func, wait, true, combine || function(acc,newargs) { return newargs; });
  };

La función de aceleración correspondiente actualmente usa solo el primer conjunto de argumentos (la aceleración ignora de manera efectiva las llamadas que ocurren dentro de los milisegundos de espera de una primera llamada y usa el conjunto de argumentos de la primera llamada, el rebote ignora de manera efectiva todas menos la última llamada en una secuencia que ocurre dentro del período de espera uno del otro), por lo que sugeriría lo siguiente para preservar nuevamente el comportamiento predeterminado actual

  _.throttle = function(func, wait, combine) {
    return limit(func, wait, false, combine || function(acc,newargs) { return acc || newargs; });
  };

Esta parecería la forma más fácil y general de lograr esta funcionalidad sin envoltorios excesivos para mantener las listas de argumentos, pero me interesaría saber si hay una manera fácil de lograr esto sin cambiar el guión bajo.

enhancement wontfix

Comentario más útil

Está bien, no quiero seguir insistiendo, pero en caso de que alguien vea esto más tarde y se pregunte cómo hacer lo mismo, pensé que esta era la forma más limpia sin modificar el rebote en sí mismo (lo agrego al objeto _, otros pueden preferir no para)

_.mixin({
  debounceReduce: function(func, wait, combine) {
    var allargs,
        context,
        wrapper = _.debounce(function() {
            var args = allargs;
            allargs = undefined;
            func.apply(context, args);
        }, wait);
        return function() {
            context = this;
            allargs = combine.apply(context, [allargs,  Array.prototype.slice.call(arguments,0)]);
            wrapper();
        };
    }
})

Esto da una función antirrebote que tiene sus argumentos reducidos por la función de combinación, por lo que, por ejemplo,

  delayLog = _.debounceReduce(function() { console.log(arguments); }, 5000, 
                              function(acc,args) { return (acc || []).concat(args); });
  delayLog(3,4);
  delayLog(7,8,9);

unos segundos más tarde llamará a console.log con la matriz [3,4,7,8,9]

Todos 11 comentarios

Al comentar sobre mi propia sugerencia, la llamada a combine() debe especificar el mismo contexto que la función de carga útil, por lo tanto

allargs = combine.apply(this, [allargs, slice.call(arguments,0)])

en caso de que los argumentos necesiten acceder al objeto de contexto....

Ahora debería estar fijo en el maestro. throttle debería exhibir el comportamiento correcto en el que siempre usa la última copia de sus argumentos, se dispara una vez inmediatamente y cada N segundos a partir de entonces... y se restablece N segundos después se ha producido el último desencadenante final.

Creo que su comentario cercano se aplica a otro problema (probablemente el n. ° 170), ya que el problema planteado por esta solicitud aún se aplica al maestro.
Todavía no hay una manera fácil de hacer que el rebote o la aceleración acumulen argumentos de las llamadas que se combinan, y sigo pensando que es una adición opcional útil que deja el comportamiento predeterminado sin cambios cuando no se especifica el argumento de combinación opcional.

Tienes razón. La acumulación de argumentos está fuera del alcance de Underscore; siéntase libre de guardar sus datos acumulados en un buen lugar fuera de las funciones _.throttle y _.debounce .

Es una pena, considero que el rebote es una especie de pliegue a la izquierda (reducción) en varias llamadas con tiempo de espera, por lo tanto, el acumulador ... pero es su decisión :)

Está bien, no quiero seguir insistiendo, pero en caso de que alguien vea esto más tarde y se pregunte cómo hacer lo mismo, pensé que esta era la forma más limpia sin modificar el rebote en sí mismo (lo agrego al objeto _, otros pueden preferir no para)

_.mixin({
  debounceReduce: function(func, wait, combine) {
    var allargs,
        context,
        wrapper = _.debounce(function() {
            var args = allargs;
            allargs = undefined;
            func.apply(context, args);
        }, wait);
        return function() {
            context = this;
            allargs = combine.apply(context, [allargs,  Array.prototype.slice.call(arguments,0)]);
            wrapper();
        };
    }
})

Esto da una función antirrebote que tiene sus argumentos reducidos por la función de combinación, por lo que, por ejemplo,

  delayLog = _.debounceReduce(function() { console.log(arguments); }, 5000, 
                              function(acc,args) { return (acc || []).concat(args); });
  delayLog(3,4);
  delayLog(7,8,9);

unos segundos más tarde llamará a console.log con la matriz [3,4,7,8,9]

@schmerg : esto parece tremendamente útil. ¿Estaría dispuesto a licenciar ese código bajo la licencia MIT? (¡Un "sí" será suficiente!)

@markjaquith Claro, sí. Contento de...

Si alguien viene y quiere una versión js moderna actualizada/comentada de lo anterior:

_.mixin({
  debounceReduce(func, wait, combine) {
    let allArgs; // accumulator for args across calls

    // normally-debounced fn that we will call later with the accumulated args
    const wrapper = _.debounce(() => func(allArgs), wait);

    // what we actually return is this function which will really just add the new args to
    // allArgs using the combine fn
    return (...args) => {
      allArgs = combine(allArgs,  [...args]);
      wrapper();
    };
  },
});

@kmannislands Oye, tu versión no restablece allArgs dentro wrapper() , por lo que las llamadas posteriores a func() obtienen lotes históricos de argumentos, así como el lote actual.

¿No debería ser:

const wrapper = _.debounce(() => {
    const args = allArgs;
    allArgs = undefined;
    func(args);
}, wait);

@markjaquith +1

También func( args ) difiere de la versión original que usa func.apply(context, args); .
Para el primero, args se usa como está en el objetivo func() , mientras que en el último _(código original)_ necesita usar arguments en una función normal o ( ...args ) en una función de flecha gruesa es6.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

jdalton picture jdalton  ·  4Comentarios

jezen picture jezen  ·  8Comentarios

acl0056 picture acl0056  ·  5Comentarios

githublyp picture githublyp  ·  3Comentarios

xiaoliwang picture xiaoliwang  ·  3Comentarios