Skip to content

Instantly share code, notes, and snippets.

@swannodette
Last active March 15, 2023 17:38
Show Gist options
  • Save swannodette/5888989 to your computer and use it in GitHub Desktop.
Save swannodette/5888989 to your computer and use it in GitHub Desktop.
(defn debounce
([c ms] (debounce (chan) c ms))
([c' c ms]
(go
(loop [start nil loc (<! c)]
(if (nil? start)
(do
(>! c' loc)
(recur (js/Date.) nil))
(let [loc (<! c)]
(if (>= (- (js/Date.) start) ms)
(recur nil loc)
(recur (js/Date.) loc))))))
c'))
// http://stackoverflow.com/questions/13320015/how-to-write-a-debounce-service-in-angularjs
app.factory('debounce', function($timeout, $q) {
return function(func, wait, immediate) {
var timeout;
var deferred = $q.defer();
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if(!immediate) {
deferred.resolve(func.apply(context, args));
deferred = $q.defer();
}
};
var callNow = immediate && !timeout;
if ( timeout ) {
$timeout.cancel(timeout);
}
timeout = $timeout(later, wait);
if (callNow) {
deferred.resolve(func.apply(context,args));
deferred = $q.defer();
}
return deferred.promise;
};
};
});
@swannodette
Copy link
Author

@halgari thanks for the review excellent points

@madvas
Copy link

madvas commented Aug 28, 2015

I found given implementation of debounce incorrect. The point of debounce is that it gets fired exactly once after all frequent calls end. This implementation doesn't put into channel when all calls end.
This demo of debounce is correct: Ben Alman » jQuery throttle / debounce » Examples » Debounce

So I wrote bit different debounce which I think is correct

Simple version:

(defn debounce
  ([c ms] (debounce (chan) c ms))
  ([c' c ms]
   (go
     (loop [timeout nil]
       (let [loc (<! c)]
         (when timeout
           (js/clearTimeout timeout))
         (let [t (js/setTimeout #(go (>! c' loc)) ms)]
           (recur t)))))
   c'))

Version with immediate param. This should be equivalent to above AngularJS snippet. When immediate true, first call always gets fired instantly and only after that debouncing starts. It might be desired in some cases.

(defn debounce
  ([c ms] (debounce (chan) c ms false))
  ([c ms immediate] (debounce (chan) c ms immediate))
  ([c' c ms immediate]
   (go
     (loop [start (js/Date.) timeout nil]
       (let [loc (<! c)]
         (when timeout
           (js/clearTimeout timeout))
         (let [diff (- (js/Date.) start)
               delay (if (and immediate
                              (or (>= diff ms)
                                  (not timeout)))
                       0 ms)
               t (js/setTimeout #(go (>! c' loc)) delay)]
           (recur (js/Date.) t)))))
   c'))

@aiba
Copy link

aiba commented Apr 18, 2016

See also https://gist.github.com/scttnlsn/9744501 for a version of debounce that outputs the final input after a flurry of inputs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment