Fork me on GitHub
#reagent
<
2018-11-02
>
Eric Evans00:11:25

Hello, I hope someone here can clear up my confusion on around when the UI updates in response to updated atoms with relatively slow calculations or sequences of calculations. My understanding was that anything that dereferences an atom would update at that time, but it seems that UI updates are deferred in some way. For example, in code similar to my example (see below), what I would expect to see in the ui, is 1. "ready" would change to "working ..." almost instantly 2. 3 seconds would pass, then result1 would show on the page 3. 3 more seconds and result2 would appear, and "working..." would change to "done" at almost the same time. However, what I actually see is 1. Nothing changes for 6 seconds. 2. "ready" changes to "done" and both results appear. However, I know the atom is actually updating at the expected time because the console.log outputs appear just when I would have expected. So, what am I not understanding here? And how could I get the behavior I was aiming for? BTW, the result is essentially the same if I make result1 and result2 to "reactions" instead of atoms. Thanks for any insight! Eric Code example: (def status (r/atom "ready")) (def result1 (r/atom 0)) (r/atom 0)) (defn slow-calculation1 [] ... takes 3 seconds ...) (defn slow-calculation2 [x] ... takes 3 seconds ...) (defn update-result! [] (reset! status "working ...") (console.log "working ...") (reset! result1 (slow-calculation1)) (console.log @result1) (reset! result2 (slow-calculation2 @result1)) (reset! status "done")) (defn example-component [] [:div [:p @status] [:p @result] [:input {:type "button" :value "calculate" :on-click update-result!}]])

enforser01:11:28

interesting. I don't have an answer for you, but I'm wondering if the results you're seeing occur with even very long times in slow-calculation2? What if slow-calculation takes a minute? Do you still not see result1 until after it is done?

enforser01:11:55

essentially, I'm wondering if the updates are deferred until update-result! is finished or if it's really just the deref's in example-component being delayed

justinlee02:11:11

@eric567 if slow-calculation1 is synchronous, then you are blocking the main thread, so reagent never has an opportunity to redraw

justinlee02:11:02

if you need to do long running calculations, you’ll need to push them into a webworker if you want the browser to be responsive

justinlee02:11:58

easy way to tell is to sequence them using setTimeout, which will yield to the event queue

Eric Evans03:11:54

@trailcapital The longest I've seen in practice is about 10 seconds, and I'm not sure how to make a function that just takes a long time. I'd use Thread sleep in Clojure, but that doesn't seem to work in cljs.

justinlee03:11:23

if your calculations are actually cpu-bound calculations, then the answer is definitely that you’re blocking the thread. @trailcapital was wondering whether updates are deferred until update-result! is finished, and the answer is both yes if it is synchronous and no if it is not

Eric Evans03:11:39

@justinlee Thanks! I just tried replacing (reset! result1 (slow-calculation1)) with (js/setTimeout #aide (reset! result1 (slow-calculation1)) 10), and it did sort of what I expected. (That is to say, slow-calculation2 executed in parallel, using 0 as its input, rather than the result of slow-calc1.) So... If I put the sequence of the two slow-calculations in another function and wrap it in setTimeout ... Is that what I'm meant to do? It seems like it would work, but I think you were just proposing a diagnostic experiment. Also, I don't think I really understand. For one thing, although I can see why slow-calculation1 might block the main thread, why doesn't (reset! status "working ...") case a UI update? Is the whole update-result! function blocking the main thread? If I have a long chain of "reactions", will I never see results from earlier calculations before all are completed?

justinlee03:11:43

right: the entire update-result! function is blocking the main thread. reagent doesn’t do updates synchronously. it batches updates using requestAnimationFrame (which runs every 16 ms, effectively). so if the main thread is all blocked doing something, then the next RAF will wait until it is done

justinlee03:11:09

you can’t have long running calculations unless you yield reasonably often or the UI will freeze up

justinlee03:11:20

or if you use webworkers

Eric Evans03:11:52

How does yielding work? I'm afraid I'm pretty new to Clojurescript, and my Clojure/Java background may be confusing me a bit here.

justinlee03:11:08

it’s a nightmare to do it that way. you’ll be happier with webworkers. you basically have to find the inner loop, check the time, then save enough state to restart the computation, then push that onto the event stack using setTimeout

Eric Evans03:11:13

Or perhaps you are saying webworkers is the preferred way to handle long running calculations. Pointing me in the right direction for what I should learn would be most appreciated.

justinlee03:11:21

yes, use webworkers definitely

Eric Evans03:11:55

Thanks! (Now I'd better go learn webworkers.)

justinlee03:11:27

thankfully not too hard. you just have to pass messages.

Eric Evans04:11:28

That's good. Favorite reference/example in cljs/reagent? Or should I read up the javascript stuff?

justinlee04:11:51

you’ll be using js interop

justinlee04:11:26

there might be a library, but i’d just do it using interop and follow a standard api reference like mdn

justinlee04:11:39

it’s going to be completely independent of reagent

Eric Evans04:11:42

Thanks very much again for your help! Knowing which thing I need to learn is sometimes really the key.

👍 4
Eric Evans23:11:48

Ok, now I've read up on web workers, and I think if I were writing this thing in Javascript I'd be good to go. And Javascript interop in Clojurescript is fine, but I'm a bit stuck.

Eric Evans23:11:01

The webworker has to be in its own .js file, so it has to be built separately somehow and referenced. I've found all sorts of discussion of including a :webworker target in a lein build, but I can't find an actual document or an example.

Eric Evans23:11:33

Given that I want the web worker to execute a function written in clojurescript and kick it off from within a clojurescript function (from reagent), I'd rather not have to somehow build it separately and treat it as an external javascript library. I must be missing something?

Eric Evans23:11:34

Feel free to just point me to the document or example, if there is one. It seems to be a very recent feature. Thanks for the help!

justinlee21:11:56

You will have to do some build-tool shenanigans to get it to build to a separate file. I can’t help because (1) I don’t use lein, and (2) I’ve never done a webworker in cljs before. sorry 😞

justinlee21:11:03

probably checkout #leiningen

Eric Evans04:11:05

Thanks for the reply. I'll keep looking, but there doesn't seem to be anything clear about how to do it. BTW, it isn't that I want separate files. But it seems like that's how webworkers have to be, no?

Eric Evans04:11:17

Anyhow, I suppose this probably doesn't really go in the reagent discussion at this point. After I dig around a little more, I may post another question in #clojurescript or someplace related to leiningen. And I do appreciate your pointers.

romdoq10:11:38

Thanks for the advice yesterday. I got my on-element drop handler working using reagent.core/create-class, and an atom for the ref.