Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
(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;
};
};
});

Does that first recur (recur (js/Date.)) have the correct arity for the enclosing loop? I'm confused by that part.

dball commented Jun 29, 2013

I don't see how that works either. Clojure says, "the recur expression must match the arity of the recursion point exactly".

ghoseb commented Jun 29, 2013

Looks like a typo. Line #8 should look exactly like line #12

Owner

@overthink @dball @ghoseb, thanks for the review, yes that's a typo.

halgari commented Jun 29, 2013

something to consider: pass c' in as an argument, or provide a different arity that gives you your desired default. In the JCSP docs and in my own tests, you often build sets of channels then want to wire them together, having all constructs take channels as arguments allows you to write stuff up after the channels have already been created.

Owner

@halgari thanks for the review excellent points

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 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