This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2015-08-04
Channels
- # admin-announcements (33)
- # announcements (2)
- # beginners (10)
- # boot (200)
- # cider (25)
- # cljs-dev (13)
- # clojure (75)
- # clojure-canada (2)
- # clojure-czech (1)
- # clojure-dev (16)
- # clojure-japan (7)
- # clojure-russia (20)
- # clojurescript (206)
- # clr (1)
- # cursive (24)
- # datascript (1)
- # datomic (1)
- # editors (2)
- # hoplon (136)
- # ldnclj (54)
- # off-topic (9)
- # om (5)
- # onyx (8)
- # re-frame (66)
- # reagent (19)
- # yada (21)
@mikethompson: good question. I've actually been assuming that you'd want to use it in conjunction with your existing event channel, which is certainly possible, but I suppose that combination should be examined as well.
So, goog.async.nextTick is a simple one-off callback style operation. You pass it a func to callback and it calls it as soon as possible by injecting a custom event into the js event system. So it gets called when js has gotten to it, which is asap, but allowing js to do all its gui bits.
It is certainly possible for re-frame dispatch
to be rewritten to use goog.async.nextTick.
The question then becomes, is there an advantage for dispatch
to continue to put!
onto an event channel?
What isn't happening with the event channel is the use of a transducer or buffer, because neither is really applicable. So now the channel is really just an async queue, and goog.async.nextTick already provides async behavior.
If there are going to be a lot of events dispatched per frame, then calling goog.async.nextTick for each one is probably not going to be efficient. And that is one problem that needs solving. So an event channel should help with that.
Which is a lot like what core.async does internally in its dispatcher: https://github.com/clojure/core.async/blob/master/src/main/clojure/cljs/core/async/impl/dispatch.cljs
Basically it toggles some indicators to tell that it's in the middle of running and then uses a single call to (goog.async.nextTick process-messages)
to process what's queued up in the channel so far, does a maximum of 1024 (def TASK_BATCH_SIZE 1024)
and returns/repeats.
So something like that would probably be a good model. When a call is made to re-frame's dispatch
, it adds the event to the channel, then triggers a call to goog.async.nextTick with a function that processes whatever is in the event queue, unless there is already a call to that function underway.
The question then becomes, how do you know if there is something in the channel. The way they did things in the core.async dispatcher is one way.
Something like this:
(defn next-tick
[callback]
(letfn [(step []
(put! event-chan ::re-frame-checkpoint)
(if (callback) (goog.async.nextTick step)))]
(goog.async.nextTick step)))
;; start event processing
(next-tick process-events)
Then in process-events
you would have your go-loop that would <! events off the channel until it got to one that matched ::re-frame-checkpoint
and then it would return. Since we always write that event as the first thing in our step
function we know it will always be there.
@meow: I'm curious ... what makes you say that calling goog.async.nextTick
for each event would be inefficient? (More inefficient than putting each event onto a core.async
channel). I had the opposite impression. I had the feeling that it would be MORE efficient than using core.async
... after all the browser will then handle the queue in C++. But this is just an impression -- no experience to back it up.
I'd be more worried that goog.async.nextTick
handled events in the order supplied. I wonder what guarantees there would be around a the underlying goog.async.nextTick
queue being FIFO.
@antishok: I borrowed the basics of that code from some experimenting I've been doing that is independent of re-frame. In order to make that loop breakable so that I could turn on/off the processing I set it up so that the callback (in this case process-events
) had to return a truthy. One use for this is so that hot re-loading code works as expected such as with boot reload or figwheel. I also can manually turn it on or off with a keybinding I have set up.
I hadn't really thought through if/when process-events
would return a value in this example.
@mikethompson: I have nothing to back up that assertion other than an impression, so it would be good to test it. I just tend to think of puts to a channel as cheap.
I've raised a related ticket: http://dev.clojure.org/jira/browse/ASYNC-137
That ticket might not be the ultimate solution, but it might get us closer
@meow: the reason I asked is because it seems to me that if process-events
always returned true, then next-tick
would be looping endlessly putting the ::-re-frame-checkpoint
on the channel and then removing it inside process-events
. and this looping would be occuring even if no events at all were being dispatched. might've misread though
I don't think it would have much impact but that is something that could be checked and worked around.
BTW, this repo contains interesting, related info: https://github.com/YuzuJS/setImmediate
That's the kind of sophistication that the core.async dispatching has built in so that it doesn't dispatch unnecessarily.
@antishok: so far I've been using that kind of code on channels that supply an infinite stream of data, so it hasn't been an issue.
@mikethompson: glad you made that ticket - was thinking that core.async needed a better approach as well, but goog has goog.Timer.callOnce
which would work for any amount of time.
@mikethompson: Here's one thing I was thinking. If someone had a re-frame app and was dispatching hundreds of events per frame then it just doesn't seem like doing an async call for each one that yields to the js event loop is going to be possible, but that's just an impression. Versus writing those to a channel where they will be taken out of the channel and handled inside one goog.async.nextTick.
In case of emergency, break the glass and use dispatch-sync
Can you believe that here we are in the year 2015 and we're having to deal with this browser nonsense?
@meow: don't start me
Hello.
I’m new to re-frame and I have a question on how to structure code.
I’m starting from the template, with secretary. I have handler called set-active-panel that is dispatched when the URL changes.
When the active-panel is :home-panel, I want to fetch some data from the server. My understanding is that I should register a handler for that and trigger an event, but where is the right place to trigger the event? if I do it inside set-active-panel, it’ll turn into a huge dispatching function (maybe eventually into multi-methods). Is that the correct approach?
@pupeno: we trigger it in the routes of secretary
(secretary/defroute companies "/companies" {:as params}
(dispatch [:request-companies])
{:view [:companies]
:params params})
See the todomvc example. https://github.com/Day8/re-frame/blob/master/examples/todomvc/src/todomvc/core.cljs#L18-L25
mikethompson: was that to me?
Hmm. Rereading your question ... i think I got the wrong idea about what you wanted to know
Ah, ok.
So ... you have some views and each one needs different data from the server?
Not each, but many will require getting some data from the server.
So perhaps you need to do something like (dispatch [:set-home-panel])
And the handler for that event knows it needs to load data from the server?
So it has two things to do: - set the view (some piece of state) - initiate the server load
So in the todomvc code I linked to above there's this:
(defroute "/:filter" [filter] (dispatch [:set-showing (keyword filter)]))
you'd modify that to be something like
(defroute "/:filter" [filter] (dispatch [(keyword filter)]))
Now you need to provide a handler for each view?? That's one approach.
Sorry, I have to head off
A handler for each view is not bad.
@mikethompson: Is there a good way to retry a dispatch that doesn't exist until it succeeds? I have an initial dispatch that starts things off and a couple of the events it dispatches aren't always there even though their definition file is declared earlier. Only causes problems intermittently but is a pain when it does.
Hi I’ve fixed my previous issues with issuing too much dispatch calls by throttling our event source (with core.async) as pkobrien and mikethompson suggested (even one per second is enough actually, there’s in fact no need to display things like current altitude 200 times per second to user).
Continuing to make POCs of different features we need for our app, I’ve added a component that wraps a google map. I based myself on https://github.com/reagent-project/reagent-cookbook/blob/master/recipes/google-maps/src/cljs/google_maps/core.cljs but it doesn’t provide any example of things like, updating a marker position according to some data in app-db
.
So I went on and wrote the following component, https://gist.github.com/jhchabran/e09883c3bc1b703a224d . I added questions/reasoning in the comments. I’m really unsure if I got it right, so any input would be valuable
@jhchabran: as for points 1-3 I thought I'd just mention that technically your run!
is not in the render function. only the :reagent-render
fn is a render function
antishok: I’m gonna re-read my comment, I didn’t mean to suggest it was (I know that it isn’t)
what I mean, is if I discard the whole idea of using run!, and just update the marker/panning inside the render, by simply using pos
inside :reagent-render
, it worked