How to use `setTimeout` in ClojureScript?
Asked Answered
R

3

8

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?

Recent answered 31/3, 2017 at 7:10 Comment(0)
P
16

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)
Ploce answered 31/3, 2017 at 7:38 Comment(3)
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.Recent
What is wrong with setTimeout? Take a look at https://mcmap.net/q/36107/-what-is-the-javascript-version-of-sleep.Ploce
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!Recent
S
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!"]])
Shae answered 2/4, 2017 at 5:35 Comment(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.Uria
@Uria so what is best way in this use case according to your opinion.Behm
It's setTimeoutUria
A
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")
Acriflavine answered 28/3, 2020 at 15:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.