Last active
August 29, 2025 03:29
-
-
Save oliyh/0c1da9beab43766ae2a6abc9507e732a to your computer and use it in GitHub Desktop.
Debounce in Clojure on the JVM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (import '[java.util Timer TimerTask]) | |
| (defn debounce | |
| ([f] (debounce f 1000)) | |
| ([f timeout] | |
| (let [timer (Timer.) | |
| task (atom nil)] | |
| (with-meta | |
| (fn [& args] | |
| (when-let [t ^TimerTask @task] | |
| (.cancel t)) | |
| (let [new-task (proxy [TimerTask] [] | |
| (run [] | |
| (apply f args) | |
| (reset! task nil) | |
| (.purge timer)))] | |
| (reset! task new-task) | |
| (.schedule timer new-task timeout))) | |
| {:task-atom task})))) | |
| ;; example usage | |
| (def say-hello (debounce #(println "Hello" %1))) | |
| (say-hello "is it me you're looking for?") | |
| (say-hello "Lionel") | |
| ;; one second later... | |
| ;; Hello Lionel |
this throws already cancelled for me once in a while:
java.lang.IllegalStateException: Timer already cancelled.
at java.util.Timer.sched (Timer.java:408)
java.util.Timer.schedule (Timer.java:204)
flower.watch$debounce$fn__12336.doInvoke (watch.clj:41)
this fixed it for me:
(let [timer (Timer.)
task (atom nil)]
(fn [& args]
(let [new-task (proxy [TimerTask] []
(run []
(apply f args)
(reset! task nil)
(.purge timer)))
old ^TimerTask @task]
; NOTE: we never retry this;
; an outdated value means we already scheduled a rerun,
; and we don't promise that all events get through.
(when (compare-and-set! task old new-task)
(when old (.cancel old))
(.schedule timer new-task ms))))))
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Works great!