what accounts for the large perf difference here?
cljs.user> (simple-benchmark [] (reduce + (rseq (vec (range 1000)))) 1000)
[], (reduce + (rseq (vec (range 1000)))), 1000 runs, 315 msecs
nil
cljs.user> (simple-benchmark [] (reduce + (vec (range 1000))) 1000)
[], (reduce + (vec (range 1000))), 1000 runs, 57 msecs
nil
in clojure the reverse reduce is only 2x slowergood point, it was firefox, same cljs version.
I am rewriting a callback hell to core async channels, and I have a dilemma: not sure how to solve situations where there is a need to fork based on state after a remote call What needs to happen is that there is a form filled out where parts of it need to be saved before than others (don't ask why, it's how the backend works) For user convenience pressing the last save button actually means that I check if anything that needs to be saved gets saved first, then I call the last save. My issue is that if there is any error, validation or otherwise, then I don't want to proceed with the save, just show feedback. And I don't know where to put the conditional that decides it. I would like to do it in the event handler with the rest of the relevant code, but that leads me to the idea that I should use a callback channel that is used for only one message. Seems like I would be back from where I started?
I watched that talk and about 2 dozen other CSP talks, I used csp in javascript a lot.
I just never liked callback channels, that's all.
I was hoping there is an alternative
backpressure might be relevant, but overflow? rate limiting? we are talking about a submit button on a form...
im very new, but it would reason that you could have a shared submission channel and a per-save-action reply channel which is provided to the submission channel with the save request. Once all of the save tasks and their reply channels are submitted, map over the reply channels with a take. Correlate the responses and display errors associated with the individual save requests.
for the most part, it could be elegant and fit nicely into a threading macro
the 'per-save-action reply channel' is what I referred to as a 'callback channel'
It really doesn't seem any more elegant than callback hell. What if I need to add another fork? I create another channel? How is that different from passing callbacks?
do you see any benefit at the response correlation point, compared to callbacks?
response correlation point?
is that the event handler?
(def sink (chan 10)
(def saves [{} {}]
(defn publish-to-sink [s]
(go
(let [reply (chan)]
(>! sink [reply s]))))
(defn wait-for-response [[c s]]
(go
[s (<! c)]))
(defn correlate-responses [coll]
... sort saves and responses from chan here ...
{:success true/false :message "..."})
->> saves
(map publish-to-sink)
(map wait-for-response)
correlate-responses
then a handler to read from the sink channel, perform your real save action
the forking additional channels im unsure
I understand I should post code with this question, but please indulge a shot in the dark, as the code canāt be posted and I am unable to duplicate the problem. I have a create-cljs-app Reagent project running via cider/emacs. When evaluating a function that updates an atom while in the containing fns.cljs buffer via cider, everything works and the UI updates in the browser. However, when that function is imported and called via an :on-click handler in another file containing a button, the function only partially runs. The function attached to the buttonās :on-click with an #(fns/thefunction) style āanonymousā function. Iām only asking in case this is a frequent gotcha or anything for which a troubleshooting tip pops out for anyone.
That same function attached to the :on-click runs fine in the other file, btw, if evaluated alone with cider as (fns/thefunction)
So this has something to do with cider evaluation or with button attachment, probably.
I just created a test function and imported it and added it to a test button and thatās not working eitherā¦
;; in fns.cljs
(defn test-function []
(prn "this ran")
(prn "this ran too"))
;; in ui.cljs
(defn test-button []
[:div
[:input {:type "button" :value "test"
:on-click #(fns/test-function)}]])
(fns/test-function)
runs in ui.cljs when evaluated with cider.
the state is kept in state.cljsā¦ maybe thereās a problem with my keeping all these things in different files.
Iām importing them into the other files left and right.
but in the case of the test-function, it just has two prn
ās, so seems as simple as can be.
this works:
(defn test-button []
[:div
[:input {:type "button" :value "test"
:on-click #(js/alert "test")}]])
You have a form, it requires two (remote) API calls (I presume, HTTP, but not important) - is that about right? core.async is not some magic dust that youāll sprinkle and complexity will go away. Like you said, you have some complexity already baked in, unfortunately, in that you need to make two separate API callsā¦ I would just write a long (go ā¦) that does everything in order and has the if/else statements in order with takes like (go (<! my-resp-1-ch) ;do something (<! my-resp-2-ch) ;do something else ) At least it will look sequential in code.
but not
(defn test-function []
(js/alert "yoyo"))
defined in fns.cljs, and called in ui.cljs via the button.
This is mysterious. Alerts work, but not prn
ās.The power of core.async is that it allows you to build nice abstractions, but it is much better in somewhat of a āgreenfieldā environment where you can design things from the ground-up.
further thoughts in this threadā¦ Iāve posted faaar too much already.
Let me know if I can help with anything else.
If you post some code, I would be happy to give feedback.
I found the prn
ās in the browser consoleā¦ thatās figured outā¦ cider called functions print to the repl, of course, and the function when called in the browser prn
s to the console. Doh.
So itās starting to look like the function Iām calling is the problem when attached to :on-clickā¦ calling it via cider standalone in ui.cljs works as mentioned, but not when the button it is attached to is clicked in the ui. The function modifies an atom with a somewhat lengthy map
function.
map
is lazy, calling it from the repl will force its evaluation so that the result may be printed but I'm not sure if that happens with :on-click
handlers
also, mixing side-effects (e.g. modifying an atom) with lazy sequences (`map`) is usually not encouraged
your immediate problem might be solved with doall
, see https://clojuredocs.org/clojure.core/doall (well, actually dorun
might be better for your use case if you don't need the return value)
youāre psychicā¦ Iām staring at the doall
in the called function. I had put it in, but Iāve zeroed in on it as a likely problem. Iāll try dorun
instead now.
hmmā¦ doall
hadnāt worked, and dorun
doesnāt either, but I think weāre in the zone.
something to do with lazy evaluation when called by the :on-click or something, not sure.
the horrible sloppy convoluted modification of state by a long tedious inefficient map
might be something for me to think about, too.
my main UI element is driven by an atom; clicking this button resets! another atom with data and maps over it to update the first atom.
as in, maps over the second atom, and searches the first atom for matching things to update.
Too few details. What's your ClojureScript version? What engine is running the code? Here's my result for Node:
ClojureScript 1.10.844
cljs.user=> (simple-benchmark [] (reduce + (rseq (vec (range 1000)))) 1000)
[], (reduce + (rseq (vec (range 1000)))), 1000 runs, 83 msecs
nil
cljs.user=> (simple-benchmark [] (reduce + (vec (range 1000))) 1000)
[], (reduce + (vec (range 1000))), 1000 runs, 38 msecs
nil
I am not really asking for code, rather, I would like to know how response channels are different from callback functions
Understood š All I said is that if you have example code, Iād be happy to discuss.
Otherwise often Iām not sure if Iām falling into abstract discussion that might be misunderstanding the problem.
left out reading from a channel but I did that too in code:)
Btw, you shouldnāt be needing to ācloseā channels. In my experience, closing channels is pretty rare.
(been using core.async since ~2014 or so, if thatās any reference š )
This is an old video by David Nolen, but I believe still very much relevant (and he does the presentation in CLJS/JS, so it will probably apply to what youāre doing) https://www.youtube.com/watch?v=AhxcGGeh5ho
Generally, to bring some more āsanityā into your HTTP requests/responses, you can have a global channel that all HTTP responses arrive on; But weāre back to the abstraction building, you need to build that yourself, core.async is just the toolbox with which you can build a vehicle š
> i did the same thing you said, have a long go that does everything, but just to replace a single callback, now i have to create a channel pass the channel push to the channel and close the channel. A few differences from an architectural perspective: ā¢ Using channels gives you the opportunity to deal with backpressure, overflow, rate limiting, etc. ā¢ Using channels gives you more flexibility for deciding when an event should be processed. It doesn't have to be processed immediately when the event is produced ā¢ Channels help decouple parts of your system which can make it easier to test. As more of the system becomes decoupled, it's more feasible to test parts in isolation Sure, replacing a single callback isn't going to help that much. It's like untangling one thread of a hairball. Hopefully, once enough parts become disentangled, the system will be easier to change, update, test, and reason about.
It depends on what you care about. Can your UI take responses in any order? Ideally, yes, that would avoid a lot of complexity. But thereās definitely cases where you might want to ensure at least certain events are ordered (say, you want to save the user to your database before reporting it to analytics); A number of cases, and a number of design choices to make. CSP/core.async can help guide you to a small degree, but will not make sane choices for you.
One of the huge wins I found when using core async was that it greatly simplified dealing with async errors, especially when combined with asynchronous processes that require time outs.
Yesā¦ Timeouts are a huge winā¦ With regular promises/callbacks how do you even handle timeouts? (Iāve actually never dealt with JS at that level of sophistication, most of my frontend work has been CLJS) Do you use some mutable state? :man-shrugging:
My messy function uses walk/postwalkā¦ I wonder if thereās a lazy evaluation issue in there.
YEEEEEESSSS!!
that was it.
I guess thereās Promise.race ā¦ Ok. š
I just wrapped the whole for
wrapped walk/postwalk business in a dorun
and it works!
you led me to the solution @https://app.slack.com/team/UC506CB5Wā¦ thank you!
Before core.async, I used Deferreds (in JS, Python, and c++): ā¢ https://twistedmatrix.com/documents/16.2.0/core/howto/defer.html ā¢ https://api.jquery.com/jquery.deferred/ ā¢ We wrote our own deferred library for c++ It's not as clean as core.async, but it was an improvement over raw callbacks.
issue solved thanks to @lasse.maattaā¦ it was a lack of a doall/dorun in the function called by the button.
Many promise libraries do much of the same stuff.
jQuery.Deferred looks like an OOP mutable API over callbacks š
(I have never used it)
Something caused some of my ClojureScript tests to start failing like this:
Uncaught exception, not in assertion.
expected: nil
actual: #object[ReferenceError ReferenceError: $f is not defined]
Is there any way to get a hold of the stack trace for the error, or any other additional information that might help figure out the root cause? Frustratingly, the exception is only thrown when I run my tests from the command line, not when I run them from the REPL with (cljs.test/run-tests ,,,)
.I know there's https://clojure.atlassian.net/browse/CLJS-3162, but looks like that was never merged.
Never mind. Looks like good old rm -rf target && .shadow-cljs
fixed the issue. :man-shrugging::skin-tone-2:
Upgrading to the latest ClojureScript and friends. I donāt yet know how to narrow this down, has anyone seen something like this? Note the !
in the varname. JavaScript does not like that.
var file_$Users$danie$_m2$repository$org$clojure$google_closure_library$0_0_20201211_3e6c510d$google_closure_library_0_0_20201211_3e6c510d_jar!$goog$log$log$classdecl$var0
Looks like a munged url for a resource in a jar
;; this is inside a subcription, only relevant effects appear based on :type from effect, and inside a go loop too
(let [{:keys [payload success-channel error-channel] :as effect } (<! effect-chan)
:keys [stuff needed] payload
response (!< (http/post ,,,))]
(case (:status response)
201 (do (set-status-and-result)
(>! state successful-put)
(>! success-channel response))
200 (do (set-status-and-result)
(>! state successful-post)
(>! success-channel response))
;otherwise errors
(do (>! state error)
(>! error-channel response))))
;; and in the event handlers (onClick, onLoad, onResize etc.)
(let [succ-chan (chan)
err-chan (chan)
(go (>! effects-chan {:type above-effect
:payload payload
:success-channel succ-chan
:error-channel err-chan})
(alt! succ-chan handle-success err-chan handle-error)
(close! succ-chan)
(close! err-chan)))
So this is code, I kinda expect that there are many better ways to achieve this. I also wish to be able to define succ/err effect reactions globally and locally too. Above would be the local example, and global I would write it inside the effect handler, but I am not sure how, because I think it's bad to intertwine the basic logic that I wrote above with specific business stuff that might change if the design (i.e. graphics, visuals, language, client interface) changes.
Somehow I would like to be able to just write a tree of actions and effects that are defined elsewhere, is that possible?Is it possible to send a byte array
using transit-cljs
(without me manually encoding the byte array to base64 or something) ?
I guess I should just try and see what happens!
In this case, removing this solved it /cc @dnolen
:language-out :ecmascript5
(I do not have a small repro for this, just a heads up)
@danie hard to say w/o a reproducer, this looks like macro stuff from somewhere