Underscore: Suggestion : _.debounce et _.throttle prennent des paramètres supplémentaires pour savoir comment combiner les arguments

Créé le 22 sept. 2011  ·  11Commentaires  ·  Source: jashkenas/underscore

Si j'utilise _.debounce() pour créer une fonction anti-rebond et que je l'appelle 3 fois de suite avec 3 ensembles d'arguments différents, alors (à partir de la v1.1.7) la fonction de charge utile enveloppée sera finalement appelée avec les arguments spécifiés par le 3e appel - c'est-à-dire que les premier et deuxième arguments sont ignorés.

Bien que cela soit souvent valide (et ce que fait généralement l'anti-rebond des clés, donc une valeur par défaut raisonnable), je me retrouve à vouloir utiliser l'anti-rebond pour accumuler des arguments, par exemple j'ai un appel AJAX qui peut obtenir plusieurs clés à la fois, donc j'utilise l'anti-rebond pour mettre en mémoire tampon clés pendant une seconde, puis émettez une demande combinée.

Ma suggestion est donc que debounce prend un 3ème argument optionnel "combine" qui sera appelé avec 2 args,

  • le 1er étant la liste des arguments accumulés jusqu'à présent (éventuellement indéfinis - c'est-à-dire pas de liste au premier appel)
  • le 2ème étant la liste des arguments du dernier appel (éventuellement une liste vide)
    et renvoie la nouvelle liste des arguments accumulés. Lorsque la fonction de charge utile est appelée, la liste accumulée des arguments est effacée.

Si aucune valeur n'est transmise pour le paramètre de combinaison, la combinaison par défaut préserve le comportement existant
function(acc, newargs) { return newargs; }
mais vous pouvez également décider d'utiliser le premier ensemble d'arguments
function(acc, newargs) { return acc || newargs; }
ou ce que je veux faire, c'est-à-dire simplement ajouter tous les arguments
function(acc,newargs) { return (acc || []).concat(newargs); }
et bien sûr d'autres voudront peut-être faire quelque chose de plus fantaisiste

Cela nécessiterait la modification suivante de la fonction de limite interne

  // 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);
    };
  };

puis une modification de debounce pour accepter et transmettre le nouvel argument avec la valeur par défaut s'il n'est pas spécifié.

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

La fonction de régulation correspondante utilise actuellement le premier ensemble d'arguments seul (la régulation ignore efficacement les appels qui se produisent dans les millisecondes d'attente d'un premier appel et utilise le premier ensemble d'arguments d'appel, l'anti-rebond ignore effectivement tous les appels sauf le dernier dans une séquence se produisant pendant la période d'attente les uns des autres), je suggérerais donc ce qui suit pour préserver à nouveau le comportement par défaut actuel

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

Cela semblerait le moyen le plus simple et le plus général d'obtenir cette fonctionnalité sans emballages excessifs pour maintenir les listes d'arguments, mais je serais intéressé de savoir s'il existe un moyen simple d'y parvenir sans changer le trait de soulignement.

enhancement wontfix

Commentaire le plus utile

OK, ne pas continuer à taper dessus, mais au cas où quelqu'un regarderait cela un peu plus tard et se demanderait comment faire la même chose, j'ai pensé que c'était à peu près la manière la plus propre sans modifier le debounce lui-même (je l'ajoute à l'objet _ , d'autres peuvent préférer ne pas pour)

_.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();
        };
    }
})

Cela donne une fonction anti-rebond dont les arguments sont réduits par la fonction de combinaison, par exemple,

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

appellera quelques secondes plus tard console.log avec le tableau [3,4,7,8,9]

Tous les 11 commentaires

Commentant ma propre suggestion, l'appel à combine() doit spécifier le même contexte que la fonction de charge utile, d'où

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

au cas où les arguments auraient besoin d'accéder à l'objet de contexte...

Devrait maintenant être fixé sur le maître. throttle devrait afficher le comportement correct où il utilise toujours la dernière copie de vos arguments, se déclenche une fois immédiatement, et toutes les N secondes ensuite ... et se réinitialise N secondes après le dernier déclencheur final s'est produit.

Je pense que votre commentaire proche s'applique à un autre problème (probablement # 170), car le problème soulevé par cette demande s'applique toujours au maître.
Il n'y a toujours pas de moyen facile de faire en sorte que les arguments anti-rebond ou étranglement accumulent les arguments des appels qui sont combinés, et je pense toujours que c'est un ajout facultatif utile qui laisse le comportement par défaut inchangé lorsque l'argument de combinaison facultatif n'est pas spécifié.

Ah, tu as raison. L'accumulation d'arguments sort du cadre de Underscore - n'hésitez pas à stocker vos données accumulées dans un bon endroit en dehors des fonctions _.throttle et _.debounce .

C'est dommage, je considère que le debounce est une sorte de fold-left (reduce) sur plusieurs appels avec timeout d'où l'accumulateur... mais c'est votre appel :)

OK, ne pas continuer à taper dessus, mais au cas où quelqu'un regarderait cela un peu plus tard et se demanderait comment faire la même chose, j'ai pensé que c'était à peu près la manière la plus propre sans modifier le debounce lui-même (je l'ajoute à l'objet _ , d'autres peuvent préférer ne pas pour)

_.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();
        };
    }
})

Cela donne une fonction anti-rebond dont les arguments sont réduits par la fonction de combinaison, par exemple,

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

appellera quelques secondes plus tard console.log avec le tableau [3,4,7,8,9]

@schmerg - cela semble extrêmement utile. Seriez-vous prêt à licencier ce code sous la licence MIT ? (Un "oui" suffira !)

@markjaquith Bien sûr - oui. Heureux de...

Si quelqu'un vient et veut une version js moderne mise à jour/commentée de ce qui précède :

_.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 Hé, votre version ne réinitialise pas allArgs à l'intérieur wrapper() , donc les appels ultérieurs à func() obtiennent des lots historiques d'arguments ainsi que le lot actuel.

Ne devrait-il pas être :

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

@markjaquith +1

De plus, func( args ) diffère de la version originale qui utilise func.apply(context, args); .
Pour le premier, args est utilisé tel quel dans la cible func() , alors que dans le dernier _(code original)_ vous devez utiliser soit arguments dans une fonction normale ou ( ...args ) dans une fonction de flèche grasse es6.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

jdalton picture jdalton  ·  6Commentaires

xiaoliwang picture xiaoliwang  ·  3Commentaires

acl0056 picture acl0056  ·  5Commentaires

arieljake picture arieljake  ·  4Commentaires

arypbatista picture arypbatista  ·  3Commentaires