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;
};
};
});
@overthink

This comment has been minimized.

Show comment Hide comment
@overthink

overthink Jun 29, 2013

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

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

@dball

This comment has been minimized.

Show comment Hide comment
@dball

dball 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".

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

This comment has been minimized.

Show comment Hide comment
@ghoseb

ghoseb Jun 29, 2013

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

ghoseb commented Jun 29, 2013

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

@swannodette

This comment has been minimized.

Show comment Hide comment
@swannodette

swannodette Jun 29, 2013

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

Owner

swannodette commented Jun 29, 2013

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

@halgari

This comment has been minimized.

Show comment Hide comment
@halgari

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

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.

@swannodette

This comment has been minimized.

Show comment Hide comment
@swannodette

swannodette Jun 29, 2013

@halgari thanks for the review excellent points

Owner

swannodette commented Jun 29, 2013

@halgari thanks for the review excellent points

@madvas

This comment has been minimized.

Show comment Hide comment
@madvas

madvas 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'))

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

This comment has been minimized.

Show comment Hide comment
@aiba

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

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