6

I'm trying to make a sleep function in ClojureScript (w/ Reagent):

(ns cljweb.webpage
  (:require [reagent.core :as reagent]))

(def temp-atom (reagent/atom 0))

(defn sleep [msec]
  (js/setTimeout (fn []) msec)) 

(defn page []
  [:div
   [:p @temp-atom]
   [:button
    {:on-click
      (fn []
        (sleep 3000) 
        (swap! temp-atom inc))}
    "Click me!"]])

For some reason, this doesn't sleep properly - when I click the "Click me!" button, temp-atom increments instantly - when I time it, by putting this after in page:

[:p (time (sleep 3000))]

I get this in the console:

"Elapsed time: 0.015000 msecs"

What did I do wrong in the code?

Qwerp-Derp
  • 457
  • 6
  • 20

3 Answers3

13

Javascript's setTimeout function accepts two arguments: function and timeout in milliseconds. Its contract is to run the received function after the timeout passes.

Your code doesn't pass the function you would like to execute after 3 seconds but instead passes a no-op function ((fn [])).

Your sleep function should look like this (and it would be better named timeout or you could just call js/setTimeout directly in your on-click handler):

(defn sleep [f ms]
  (js/setTimeout f ms))

You also need to change how you call this function:

(sleep #(swap! temp-atom inc) 3000)

Or with calling js/setTimeout directly:

(js/setTimeout #(swap! temp-atom inc) 3000)
Piotrek Bzdyl
  • 12,019
  • 1
  • 26
  • 44
  • 2
    Hmmm... is there a way to achieve `sleep` without using `setTimeout`? That's my primary goal. I'm pretty sure that make my question an XY question, though, so I may have to post a new question. – Qwerp-Derp Mar 31 '17 at 07:39
  • 2
    What is wrong with `setTimeout`? Take a look at http://stackoverflow.com/a/39914235/597473. – Piotrek Bzdyl Mar 31 '17 at 07:41
  • Aww... I saw the results of that question, and it's kinda disappointing. In order to actually implement the solution of the newest solution, I have to install another library, which is a bummer. Thanks anyway! – Qwerp-Derp Mar 31 '17 at 07:46
10

With ClojureScript, the best way to write asynchronous code is with the CoreAsync library. In your case, take a look at the timeout function:

(ns cljweb.webpage
  (:use-macros [cljs.core.async.macros :only [go]]
  (:require [reagent.core :as reagent]
            [cljs.core.async :refer [<! timeout]]))

(def temp-atom (reagent/atom 0))

(defn page []
   [:div
     [:p @temp-atom]
     [:button
       {:on-click
         (fn []
          (go
            (<! (timeout 3000))
            (swap! temp-atom inc)))}
         "Click me!"]])
Asher
  • 1,019
  • 9
  • 22
  • 3
    I have to say it's not the best way. There are several ways in the language to deal with asynchronous code. Different tasks require different tools. In this case core.async is an overkill. Also, it's core.async not CoreAsync. – Nek Feb 15 '18 at 14:51
  • @Nek so what is best way in this use case according to your opinion. – Uchiha Itachi Jul 09 '19 at 15:07
  • It's setTimeout – Nek Jul 10 '19 at 07:25
0

There is a way to implement such functionality using goog.async.Debouncer

Here is an example:

(ns example.utils
  (:require [goog.async.Debouncer]))

(defn debounce [f interval]
  (let [dbnc (goog.async.Debouncer. f interval)]
    (fn [& args] (.apply (.-fire dbnc) dbnc (to-array args)))))

(defn save-input! [input]
  (js/console.log "Saving input" input))

(def save-input-debounced!
  (debounce save-input! 3000))

(save-input-debounced! "hi")
Aliaksandr Sushkevich
  • 7,264
  • 6
  • 29
  • 36