Underscore: Vorschlag: _.debounce und _.throttle nehmen zusätzliche Parameter zum Kombinieren von Argumenten

Erstellt am 22. Sept. 2011  ·  11Kommentare  ·  Quelle: jashkenas/underscore

Wenn ich _.debounce() verwende, um eine entprellte Funktion zu erstellen, und sie dann 3 Mal hintereinander mit 3 verschiedenen Argumentsätzen aufrufe, wird (ab v1.1.7) die umschlossene Nutzlastfunktion schließlich mit den von der angegebenen Argumenten aufgerufen 3. Aufruf - das heißt, das erste und das zweite Argument werden verworfen.

Obwohl dies oft gültig ist (und was das Entprellen von Schlüsseln normalerweise tut, daher ein vernünftiger Standard), möchte ich Debounce verwenden, um Argumente zu sammeln, zum Beispiel habe ich einen AJAX-Aufruf, der mehrere Schlüssel gleichzeitig abrufen kann, also verwende ich Debounce zum Puffern Halten Sie die Tasten eine Sekunde lang gedrückt und senden Sie dann eine kombinierte Anfrage.

Mein Vorschlag ist daher, dass Debounce ein optionales drittes "combine"-Argument verwendet, das mit 2 Argumenten aufgerufen wird,

  • die erste ist die Liste der bisher angesammelten Argumente (möglicherweise undefiniert - dh keine Liste beim ersten Aufruf)
  • das zweite ist die Liste der Argumente für den letzten Anruf (möglicherweise eine leere Liste)
    und gibt die neue Liste der akkumulierten Argumente zurück. Wenn die Payload-Funktion aufgerufen wird, wird die akkumulierte Liste von Argumenten gelöscht.

Wenn für den Combine-Parameter kein Wert übergeben wird, behält das Standard-Combine das vorhandene Verhalten bei
function(acc, newargs) { return newargs; }
Sie können sich aber auch dafür entscheiden, den ersten Satz von Argumenten zu verwenden
function(acc, newargs) { return acc || newargs; }
oder was ich tun möchte, ist einfach alle Argumente anhängen
function(acc,newargs) { return (acc || []).concat(newargs); }
und natürlich möchten andere vielleicht etwas schickeres machen

Dies würde die folgende Änderung an der internen Begrenzungsfunktion erfordern

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

und dann eine Änderung zum Entprellen, um das neue Argument mit dem Standardwert zu akzeptieren und weiterzugeben, falls nicht angegeben.

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

Die entsprechende Drosselungsfunktion verwendet derzeit nur den ersten Satz von Argumenten (Drosselung ignoriert effektiv Aufrufe, die innerhalb von Warte-Millisekunden nach einem ersten Aufruf erfolgen, und verwendet den ersten Aufrufsatz von Argumenten, Debounce ignoriert effektiv alle bis auf den letzten Aufruf in einer Sequenz, die innerhalb der Wartezeit auftritt voneinander), daher würde ich Folgendes vorschlagen, um das aktuelle Standardverhalten wieder beizubehalten

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

Dies scheint der einfachste und allgemeinste Weg zu sein, diese Funktionalität ohne übermäßige Wrapper zur Pflege der Argumentlisten zu erreichen, aber ich würde gerne wissen, ob es einen einfachen Weg gibt, dies zu erreichen, ohne den Unterstrich zu ändern.

enhancement wontfix

Hilfreichster Kommentar

OK, um nicht weiter zu hämmern, aber falls sich jemand das irgendwann später ansieht und sich fragt, wie man dasselbe macht, dachte ich, dass dies der sauberste Weg ist, ohne die Entprellung selbst zu ändern (ich füge es dem _-Objekt hinzu, andere ziehen es vielleicht vor, es nicht zu tun zu)

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

Dies ergibt eine entprellte Funktion, deren Argumente durch die Combine-Funktion reduziert werden, also zum Beispiel

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

wird einige Sekunden später console.log mit dem Array [3,4,7,8,9] aufrufen

Alle 11 Kommentare

Meinen eigenen Vorschlag kommentierend, sollte der Aufruf von Combine() daher den gleichen Kontext wie die Payload-Funktion angeben

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

falls die Argumente auf das Kontextobjekt zugreifen müssen....

Sollte jetzt auf Master behoben sein. throttle sollte das korrekte Verhalten zeigen, wo es immer die neueste Kopie Ihrer Argumente verwendet, sofort einmal und danach alle N Sekunden ausgelöst wird ... und sich N Sekunden danach selbst zurücksetzt der letzte nachlaufende Trigger ist aufgetreten.

Ich denke, Ihr knapper Kommentar bezieht sich auf ein anderes Problem (wahrscheinlich Nr. 170), da das durch diese Anfrage aufgeworfene Problem immer noch für den Master gilt.
Es gibt immer noch keine einfache Möglichkeit, Argumente aus den kombinierten Aufrufen zu entprellen oder zu drosseln, und ich denke immer noch, dass es eine nützliche optionale Ergänzung ist, die das Standardverhalten unverändert lässt, wenn das optionale Kombinationsargument nicht angegeben wird.

Ah, du hast recht. Das Sammeln von Argumenten liegt außerhalb des Bereichs von Underscore - Sie können Ihre gesammelten Daten gerne an einem guten Ort außerhalb der Funktionen _.throttle und _.debounce verstauen.

Schade, ich betrachte Debounce als eine Art Fold-Left (Reduzieren) über mehrere Anrufe mit Timeout, daher der Akkumulator ... aber es ist dein Anruf :)

OK, um nicht weiter zu hämmern, aber falls sich jemand das irgendwann später ansieht und sich fragt, wie man dasselbe macht, dachte ich, dass dies der sauberste Weg ist, ohne die Entprellung selbst zu ändern (ich füge es dem _-Objekt hinzu, andere ziehen es vielleicht vor, es nicht zu tun zu)

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

Dies ergibt eine entprellte Funktion, deren Argumente durch die Combine-Funktion reduziert werden, also zum Beispiel

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

wird einige Sekunden später console.log mit dem Array [3,4,7,8,9] aufrufen

@schmerg — das sieht enorm nützlich aus. Wären Sie bereit, diesen Code unter der MIT-Lizenz zu lizenzieren? (Ein „Ja“ genügt!)

@markjaquith Klare Sache - ja. Gerne...

Wenn jemand vorbeikommt und eine aktualisierte/kommentierte moderne js-Version des oben Genannten haben möchte:

_.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 Hey, deine Version setzt allArgs innerhalb wrapper() nicht zurück, daher erhalten nachfolgende Aufrufe von func() historische Stapel von Argumenten sowie den aktuellen Stapel.

Sollte es nicht heißen:

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

@markjaquith +1

Auch func( args ) unterscheidet sich von der Originalversion, die func.apply(context, args); verwendet.
Für ersteres wird args wie im Ziel func() verwendet, wohingegen Sie im späteren _(ursprünglichen Code)_ entweder arguments in einer normalen Funktion verwenden müssen oder ( ...args ) in einer es6-fetten Pfeilfunktion.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

afranioce picture afranioce  ·  8Kommentare

ksullivan picture ksullivan  ·  9Kommentare

acl0056 picture acl0056  ·  5Kommentare

danilopolani picture danilopolani  ·  5Kommentare

clouddueling picture clouddueling  ·  3Kommentare