Einfaches Gas in js

72

Ich suche nach einem einfachen Gas in JS. Ich weiß, dass Bibliotheken wie lodash und unterstreichen es haben, aber nur für eine Funktion ist es übertrieben, eine dieser Bibliotheken einzuschließen.

Ich habe auch geprüft, ob jquery eine ähnliche Funktion hat - konnte nicht finden.

Ich habe einen funktionierenden Gashebel gefunden , und hier ist der Code:

function throttle(fn, threshhold, scope) {
  threshhold || (threshhold = 250);
  var last,
      deferTimer;
  return function () {
    var context = scope || this;

    var now = +new Date,
        args = arguments;
    if (last && now < last + threshhold) {
      // hold on to it
      clearTimeout(deferTimer);
      deferTimer = setTimeout(function () {
        last = now;
        fn.apply(context, args);
      }, threshhold);
    } else {
      last = now;
      fn.apply(context, args);
    }
  };
}

Das Problem dabei ist: Es löst die Funktion nach Ablauf der Drosselzeit erneut aus. Nehmen wir also an, ich habe eine Drosselklappe gedrückt, die alle 10 Sekunden beim Drücken der Taste ausgelöst wird. Wenn ich zweimal die Taste drücke, wird der zweite Tastendruck nach 10 Sekunden immer noch ausgelöst. Ich möchte dieses Verhalten nicht.

Mia
quelle
3
1. jQuery hat ein Plugin benalman.com/projects/jquery-throttle-debounce-plugin 2. Warum nicht einfach die Gasimplementierung von underscore / lodash verwenden?
Oleg
@Oleg ist es möglich, nur den Gashebel zu verwenden, ohne die gesamte Bibliothek zu importieren?
Mia
1
Könnten Sie ein Beispiel aufstellen oder zumindest den Anwendungsfall etwas besser erklären? Im Allgemeinen ist ein Tastendruck ziemlich einfach einzurichten, so etwas -> jsfiddle.net/a3w6pLbj/1
adeneo

Antworten:

89

Ich würde den Quellcode underscore.js oder lodash verwenden, um eine gut getestete Version dieser Funktion zu finden.

Hier ist die leicht modifizierte Version des Unterstrichcodes, um alle Verweise auf underscore.js selbst zu entfernen:

// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
function throttle(func, wait, options) {
  var context, args, result;
  var timeout = null;
  var previous = 0;
  if (!options) options = {};
  var later = function() {
    previous = options.leading === false ? 0 : Date.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };
  return function() {
    var now = Date.now();
    if (!previous && options.leading === false) previous = now;
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
};

Bitte beachten Sie, dass dieser Code vereinfacht werden kann, wenn Sie nicht alle Optionen benötigen, die die Unterstützung unterstreichen.

Nachfolgend finden Sie eine sehr einfache und nicht konfigurierbare Version dieser Funktion:

function throttle (callback, limit) {
    var waiting = false;                      // Initially, we're not waiting
    return function () {                      // We return a throttled function
        if (!waiting) {                       // If we're not waiting
            callback.apply(this, arguments);  // Execute users function
            waiting = true;                   // Prevent future invocations
            setTimeout(function () {          // After a period of time
                waiting = false;              // And allow future invocations
            }, limit);
        }
    }
}

Bearbeiten 1: Ein weiterer Verweis auf den Unterstrich wurde entfernt, danke an @Zettams Kommentar

Bearbeiten 2: Vorschlag zu lodash und möglicher Code-Vereinfachung hinzugefügt, danke an den Kommentar von @lolzery @wowzery

Bearbeiten 3: Aufgrund beliebter Anfragen habe ich eine sehr einfache, nicht konfigurierbare Version der Funktion hinzugefügt, die aus dem Kommentar von @vsync übernommen wurde

Clément Prévost
quelle
46
sieht für mich nicht einfach aus. Dies ist ein gutes Beispiel für einfach
vsync
9
Dies ist in der Tat nicht einfach. Aber es ist produktionsbereit und Open Source.
Clément Prévost
11
Einer der Gründe, warum dies nicht so einfach ist wie der mit @vsync verknüpfte, ist, dass er nachfolgende Aufrufe unterstützt. Wenn Sie die resultierende Funktion zweimal aufgerufen haben, führt dies zu zwei Aufrufen der umschlossenen Funktion: einmal unmittelbar und einmal nach der Verzögerung. In dem mit verknüpften vsync führt dies zu einem einzelnen sofortigen Aufruf, jedoch nach der Verzögerung zu keinem. In vielen Fällen ist es sehr wichtig, den nachfolgenden Anruf zu erhalten, um die letzte Größe des Ansichtsfensters oder was auch immer Sie versuchen zu erhalten.
Aaronius
3
Bitte benutzen Sie dies niemals. Ich habe nicht vor, arrogant zu sein. Vielmehr möchte ich nur praktisch sein. Diese Antwort ist viel komplizierter als es sein muss. Ich habe eine separate Antwort auf diese Frage veröffentlicht, die all dies und mehr in weitaus weniger Codezeilen erledigt.
Jack Giffin
2
@Nico das argumentsObjekt wird immer in jeder Funktion definiert, die keine Pfeilfunktion ist: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Clément Prévost
13

Rückruf : Übernimmt die Funktion, die aufgerufen werden soll

limit : Häufigkeit, mit der diese Funktion innerhalb des Zeitlimits aufgerufen werden soll

Zeit : Zeitspanne zum Zurücksetzen der Grenzwertanzahl

Funktionalität und Verwendung : Angenommen, Sie haben eine API, mit der der Benutzer sie 10 Mal in 1 Minute aufrufen kann

function throttling(callback, limit, time) {
    /// monitor the count
    var calledCount = 0;

    /// refresh the `calledCount` varialbe after the `time` has been passed
    setInterval(function(){ calledCount = 0 }, time);

    /// creating a closure that will be called
    return function(){
        /// checking the limit (if limit is exceeded then do not call the passed function
        if (limit > calledCount) {
            /// increase the count
            calledCount++;
            callback(); /// call the function
        } 
        else console.log('not calling because the limit has exceeded');
    };
}
    
//////////////////////////////////////////////////////////// 
// how to use

/// creating a function to pass in the throttling function 
function cb(){
    console.log("called");
}

/// calling the closure function in every 100 milliseconds
setInterval(throttling(cb, 3, 1000), 100);

Vikas Bansal
quelle
6
@lolzerywowzery Es ist nicht so, dass Ihre Antwort weniger kompliziert ist als es sein muss
Denny
1
Ich würde vorschlagen, den Rückruf wie folgt auszulösen: callback(...arguments)um die ursprünglichen Argumente beizubehalten. sehr praktisch
vsync
Dies sollte die akzeptierte Antwort sein. Simpel und einfach.
Gaurang Tandon
@Denny Diese Antwort hat enorme Browser-Ressourcen tailliert. Es startet eine völlig neue Intervalling-Funktion für jeden einzelnen Handler, den es erstellt. Selbst nachdem Sie den Ereignis-Listener entfernt haben, werden durch die fortlaufende Abfrage die Computerressourcen aufgebraucht, was zu einer hohen Speichernutzung, Einfrieren und Seitenbausteinen führt.
Jack Giffin
4
Bitte verwenden Sie diese Antwort niemals im Produktionscode. Dies ist sehr positiv für schlechte Programmierung. Angenommen, Sie haben 1000 Schaltflächen auf Ihrer Seite (was sich nach viel anhört, aber denken Sie noch einmal darüber nach: Schaltflächen verstecken sich überall: in Popups, Untermenüs, Bedienfeldern usw.) und möchten, dass jede Schaltfläche alle 200 Sekunden höchstens einmal ausgelöst wird Sie würden wahrscheinlich alle zur gleichen Zeit ausgelöst, alle 333 Millisekunden (oder dreimal pro Sekunde). Es würde eine massive Verzögerungsspitze geben, wenn alle diese Timer erneut überprüft werden müssen. Diese Antwort missbraucht vollständig setIntervalfür Zwecke, die nicht beabsichtigt waren.
Jack Giffin
12

Was ist damit?

function throttle(func, timeFrame) {
  var lastTime = 0;
  return function () {
      var now = new Date();
      if (now - lastTime >= timeFrame) {
          func();
          lastTime = now;
      }
  };
}

Einfach.

Vielleicht möchten Sie einen Blick auf die Quelle werfen .

Smartmouse
quelle
1
Dies ist die sauberste minimale Implementierung auf der Seite.
Lawrence Dol
Für mich funktioniert das nur mit - Date.now()anstattnew Date()
Ian Jones
11

Wenn Sie der Diskussion hier (und für neuere Besucher) hinzufügen, dass der Grund dafür, dass Sie das fast de facto throttlevon nicht verwenden, darin lodashbesteht, ein kleineres Paket oder Bundle zu haben, ist es möglich, nur throttleIhr Bundle anstelle der gesamten lodashBibliothek aufzunehmen. Zum Beispiel in ES6 wäre es so etwas wie:

import throttle from 'lodash/throttle';

Es gibt auch ein throttleeinziges Paket von lodashaufgerufen lodash.throttle, das mit einem einfachen importin ES6 oder requirein ES5 verwendet werden kann.

Divyanshu Maithani
quelle
4
Überprüfte den Code. Es werden 2 Dateien importiert, was bedeutet, dass Sie 3 Dateien für eine einfache Drosselfunktion benötigen. Ein bisschen übertrieben würde ich sagen, besonders wenn jemand (wie ich) eine Drosselfunktion für ein ~ 200 Zeilen Code-Programm benötigt.
vsync
6
Ja, es wird intern verwendet debounceund isObjectdie gesamte Bundle-Größe wird auf ca. 2,1 KB reduziert . Ich nehme an, macht für ein kleines Programm keinen Sinn, aber ich würde es vorziehen, es in größeren Projekten zu
verwenden, anstatt
6

Ich brauchte nur eine Drossel- / Entprellungsfunktion für Fenstergrößenänderungsereignisse, und da ich neugierig war, wollte ich auch wissen, was diese sind und wie sie funktionieren.

Ich habe mehrere Blog-Beiträge und QAs zu SO gelesen, aber alle scheinen dies zu komplizieren, Bibliotheken vorzuschlagen oder nur Beschreibungen und keine einfachen JS-Implementierungen bereitzustellen.

Ich werde keine Beschreibung geben, da es reichlich ist. Hier ist meine Implementierung:

function throttle(callback, delay) {
    var timeoutHandler = null;
    return function () {
        if (timeoutHandler == null) {
            timeoutHandler = setTimeout(function () {
                callback();
                clearInterval(timeoutHandler);
                timeoutHandler = null;
            }, delay);
        }
    }
}

function debounce(callback, delay) {
    var timeoutHandler = null;
    return function () {
        clearTimeout(timeoutHandler);
        timeoutHandler = setTimeout(function () {
            callback();
        }, delay);
    }
}

Diese müssen möglicherweise angepasst werden (z. B. wird der Rückruf anfangs nicht sofort aufgerufen).

Sehen Sie den Unterschied in der Aktion (versuchen Sie, die Größe des Fensters zu ändern):

JSFiddle

Akinuri
quelle
Schlecht gestaltet und führt zu einer großen Verzögerung bei allem, was daran angehängt ist, wodurch die Website für den Benutzer nicht mehr reagiert.
Jack Giffin
1
@commonSenseCode Was bedeutet "funktioniert nicht einmal" überhaupt? Ich habe Demo-Code bereitgestellt. Es funktioniert eindeutig. Versuchen Sie bitte, ausführlicher zu sein. Was auch immer nicht funktioniert, ich bin mir ziemlich sicher, dass es etwas mit Ihrer Implementierung zu tun hat.
Akinuri
5

Hier ist, wie ich die Drosselfunktion in ES6 in 9LOC implementiert habe. Ich hoffe, es hilft

function throttle(func, delay) {
  let timeout = null
  return function(...args) {
    if (!timeout) {
      timeout = setTimeout(() => {
        func.call(this, ...args)
        timeout = null
      }, delay)
    }
  }
}

Klicken Sie darauf Link , um zu sehen, wie es funktioniert.

Vipin Goyal
quelle
2
Einfach, aber eher ineffektiv: Die Funktion wird verzögert, auch wenn sie nicht angemessen ist, und das ausstehende Ereignis wird nicht aktuell gehalten, was möglicherweise zu einer erheblichen Verzögerung der Verzögerung der Benutzerinteraktionen führt. Außerdem ist die Verwendung der ...Spread-Syntax unangemessen, da immer nur ein Argument an den Ereignis-Listener übergeben wird: das Ereignisobjekt.
Jack Giffin
1
@ JackGiffin: Die Verwendung von Spread ist nicht unangemessen. Nichts beschränkt die Drosselfunktion darauf, nur für einen Ereignishandler verwendet zu werden.
Lawrence Dol
1

Ich habe ein npm-Paket mit einigen Drosselfunktionen erstellt:

npm installiere den Funktionsdrossler

throttleAndQueue

Gibt eine Version Ihrer Funktion zurück, die höchstens alle W Millisekunden aufgerufen werden kann, wobei W wartet. Anrufe an Ihre Funk, die häufiger als W auftreten, werden in die Warteschlange gestellt, um alle W ms aufgerufen zu werden

throttledUpdate

Gibt eine Version Ihrer Funktion zurück, die höchstens alle W Millisekunden aufgerufen werden kann, wobei W wartet. Bei Anrufen, die häufiger als W stattfinden, ist der letzte Anruf der angerufene (der letzte hat Vorrang).

drosseln

begrenzt Ihre Funktion so, dass sie höchstens alle W Millisekunden aufgerufen wird, wobei W wartet. Anrufe über W werden abgebrochen

Almenon
quelle
1

Für diesen Zweck gibt es eine Bibliothek, Backburner.js von Ember.

https://github.com/BackburnerJS/

Du würdest es so benutzen.

var backburner = new Backburner(["task"]); //You need a name for your tasks

function saySomething(words) {
  backburner.throttle("task", console.log.bind(console, words)
  }, 1000);
}


function mainTask() {
  "This will be said with a throttle of 1 second per word!".split(' ').map(saySomething);
}

backburner.run(mainTask)
Rainb
quelle
1

Diese Drosselfunktion basiert auf ES6. Rückruffunktionen akzeptieren Argumente (Argumente) und funktionieren dennoch mit der Drosselfunktion. Sie können die Verzögerungszeit ganz nach Ihren App-Anforderungen anpassen. Für den Entwicklungsmodus wird 1 Mal pro 100 ms verwendet. Das Ereignis "oninput" ist nur ein Beispiel für den häufigen Fall seiner Verwendung:

const callback = (...args) => {
  console.count('callback throttled with arguments:', args);
};

throttle = (callback, limit) => {
  let timeoutHandler = 'null'

  return (...args) => {
    if (timeoutHandler === 'null') {
      timeoutHandler = setTimeout(() => {            
        callback(...args)
        timeoutHandler = 'null'
      }, limit)
    }
  }
}

window.addEventListener('oninput', throttle(callback, 100));

PS Wie @Anshul erklärte: Durch die Drosselung wird eine maximale Anzahl von Aufrufen einer Funktion im Laufe der Zeit erzwungen. Wie in "Führen Sie diese Funktion höchstens einmal alle 100 Millisekunden aus."

römisch
quelle
Es wartet auch 1000 Millisekunden, bevor es den Rückruf aufruft, der überhaupt nicht gut ist. Benutzer möchten eine reaktionsschnelle Seite, keinen trägen Albtraum.
Jack Giffin
Danke für deinen Kommentar. Sie können die Rückrufzeit gerne an die Anforderungen Ihrer Anwendung anpassen. Normalerweise liegt sie zwischen 100 und 500 Millisekunden. Mit diesen 1000 Millisekunden können Sie die Funktion überprüfen und debuggen.
Roman
@ JackGiffin In einigen Szenarien ist der Aufruf der Vorderkante nicht erwünscht. Ein Beispiel wäre ein automatisches Speichern.
Pettys
@pettys Wenn es keinen Aufruf der Vorderkante gibt, handelt es sich nicht um eine Drosselfunktion. Es handelt sich vielmehr um eine Entprellfunktion.
Jack Giffin
4
@ JackGiffin Ich denke nicht, dass das die richtige Unterscheidung zwischen Gas und Entprellen ist. Ich glaube, Gas ist "Nicht mehr als x mal / s aufrufen", während Entprellen "Für Sequenzen von Quellereignissen, bei denen die Lücke kleiner als x s ist, behandeln Sie die gesamte Sequenz als eine einzelne Instanz." Subtiler Unterschied, aber hier gut dargestellt: demo.nimius.net/debounce_throttle Es scheint mir, dass sowohl Gas als auch Entprellen bedeutungsvolle und nützliche Konfigurationen ohne Vorderkante haben.
Pettys
1

Versuchen Sie im folgenden Beispiel, mehrmals auf die Schaltfläche zu klicken, aber die myFuncFunktion wird nur einmal in 3 Sekunden ausgeführt. Die Funktion throttlewird mit der auszuführenden Funktion und der Verzögerung übergeben. Sie gibt einen Abschluss zurück, der in gespeichert ist obj.throttleFunc. Da nun obj.throttleFuncein Verschluss gespeichert wird, isRunningbleibt der Wert von darin erhalten.

function throttle(func, delay) {
  let isRunning;
  return function(...args) {
    let context = this;        // store the context of the object that owns this function
    if(!isRunning) {
      isRunning = true;
      func.apply(context,args) // execute the function with the context of the object that owns it
      setTimeout(function() {
        isRunning = false;
      }, delay);
    }
  }
}

function myFunc(param) {
  console.log(`Called ${this.name} at ${param}th second`);
}

let obj = {
  name: "THROTTLED FUNCTION ",
  throttleFunc: throttle(myFunc, 3000)
}

function handleClick() {
  obj.throttleFunc(new Date().getSeconds());
}
button {
  width: 100px;
  height: 50px;
  font-size: 20px;
}
    <button onclick="handleClick()">Click me</button>


Wenn wir nicht möchten, dass der Kontext oder die Argumente übergeben werden, lautet eine einfachere Version wie folgt:

function throttle(func, delay) {
  let isRunning;
  return function() {
    if(!isRunning) {
      isRunning = true;
      func()
      setTimeout(function() {
        isRunning = false;
      }, delay);
    }
  }
}

function myFunc() {
  console.log('Called');
}


let throttleFunc = throttle(myFunc, 3000);

function handleClick() {
  throttleFunc();
}
button {
  width: 100px;
  height: 50px;
  font-size: 20px;
}
<button onclick="handleClick()">Click me</button>

Ayan
quelle
0

Unten ist das einfachste Gas, das ich mir vorstellen kann, in 13 LOC. Bei jedem Aufruf der Funktion wird eine Zeitüberschreitung erstellt und die alte abgebrochen. Die ursprüngliche Funktion wird erwartungsgemäß mit dem richtigen Kontext und den richtigen Argumenten aufgerufen.

function throttle(fn, delay) {
  var timeout = null;

  return function throttledFn() {
    window.clearTimeout(timeout);
    var ctx = this;
    var args = Array.prototype.slice.call(arguments);

    timeout = window.setTimeout(function callThrottledFn() {
      fn.apply(ctx, args);
    }, delay);
  }
}

// try it out!
window.addEventListener('resize', throttle(function() {
  console.log('resize!!');
}, 200));

Alessioalex
quelle
11
Dies ist ein Debounce, kein Gas. Wenn ich diese 100 in 1000ms nenne, wird es 1 Mal nach 1200ms ausgelöst. Eine gedrosselte Funktion sollte 5 Mal
ausgelöst werden
0

Hier ist meine eigene Version von Vikas Post:

throttle: function (callback, limit, time) {
    var calledCount = 0;
    var timeout = null;

    return function () {
        if (limit > calledCount) {
            calledCount++;
            callback(); 
        }
        if (!timeout) {
            timeout = setTimeout(function () {
                calledCount = 0
                timeout = null;
            }, time);
        }
    };
}

Ich finde, dass die Verwendung setIntervalkeine gute Idee ist.

makore
quelle
0

Ich möchte auch eine einfache Lösung vorschlagen, wenn es nur eine Funktion gibt, von der Sie wissen, dass Sie sie aufrufen werden (zum Beispiel: Suchen).

Hier ist, was ich in meinem Projekt gemacht habe

let throttle;

function search() {
    if (throttle) {
      clearTimeout(throttle);
    }
    throttle = setTimeout(() => {
      sendSearchReq(str)
    }, 500);
  }

Die Suche wird beim Eingabeänderungsereignis aufgerufen

Dan Levin
quelle
1
Dies ist nicht gerade eine Drosselfunktion. Bei jedem Aufruf der search()Funktion wird das Timeout zurückgesetzt. Wenn ich also die search()Funktion über jeder Millisekunde aufrufe, wird sie nur sendSearchReqeinmal und dann nie wieder ausgeführt, anstatt alle 500 ms. Diese Funktion ist eher eine Verzögerung als eine Drossel.
Tillsanders
Dies ist Debounce nicht Gas
Eduard Jacko
0
function throttle(targetFunc, delay){
  let lastFunc;
  let lastTime;

  return function(){
    const _this = this;
    const args = arguments;

    if(!lastTime){
      targetFunc.apply(_this, args);
      lastTime = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(function(){
        targetFunc.apply(_this, args);
        lastTime = Date.now();
      }, delay - (Date.now() - lastTime));
    }
  }
}

Versuch es :

window.addEventListener('resize', throttle(function() {
  console.log('resize!!');
}, 200));
Mensch sein
quelle
0

Einfache Gasfunktion -

Hinweis: Klicken Sie weiter auf die Schaltfläche. Das Konsolenprotokoll wird zuerst beim Klicken und dann erst alle 5 Sekunden angezeigt, bis Sie weiter klicken.

HTML -

<button id='myid'>Click me</button>

Javascript -

const throttle = (fn, delay) => {
  let lastTime = 0;
  return (...args) => {
      const currentTime = new Date().getTime();
      if((currentTime - lastTime) < delay) {
        return;
      };
      lastTime = currentTime;
      return fn(...args);
  }
};

document.getElementById('myid').addEventListener('click', throttle((e) => {
  console.log('I am clicked');
}, 5000));
Avadhut Thorat
quelle
0

Wir können auch mit einem Flag implementieren.

var expensive = function(){
    console.log("expensive functionnns");
}

window.addEventListener("resize", throttle(expensive, 500))

function throttle(expensiveFun, limit){
    let flag = true;
    return function(){
        let context = this;
        let args = arguments;
        if(flag){
            expensiveFun.apply(context, args);
            flag = false;
            setTimeout(function(){
                flag = true;
            }, limit);
        }
    }
}

Ganesh Phirke
quelle