This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-08-04
Channels
- # announcements (7)
- # babashka (32)
- # beginners (106)
- # bristol-clojurians (10)
- # cider (6)
- # clj-kondo (5)
- # cljdoc (10)
- # clojure (110)
- # clojure-australia (10)
- # clojure-dev (6)
- # clojure-europe (12)
- # clojure-nl (2)
- # clojure-norway (16)
- # clojure-spec (9)
- # clojure-uk (59)
- # clojurescript (105)
- # community-development (2)
- # conjure (46)
- # cursive (12)
- # data-science (1)
- # datalog (26)
- # datomic (37)
- # docker (4)
- # emacs (10)
- # events (1)
- # fulcro (8)
- # graalvm (2)
- # jobs (1)
- # jobs-discuss (1)
- # malli (24)
- # meander (13)
- # off-topic (52)
- # pathom (4)
- # polylith (17)
- # proletarian (4)
- # react (1)
- # rewrite-clj (4)
- # shadow-cljs (56)
- # sql (21)
- # xtdb (14)
Hey. does clojurescript has any way to work with AsyncIterable inteface in JS. The JS way is to use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of. For normal async/await or promise, I found - https://clojurescript.org/guides/promise-interop
Isn’t it something you can iterate over and just treat it like a regular promise? Cljs does not support async/await
(let [async-iterator ((aget async-iterable (.- js/Symbol asyncIterator)))
handle-next (fn handle-next [value]
(let [next (.next async-iterator)]
(.then next
(fn [res]
(if (.- res done)
value
(handle-next (conj value (.- res value))))))))]
(handle-next []))
you could also throw in some handling of (reduced) or make the point to do the side effects
(defmacro async-for [binding async-iterable & body]
`(let [async-iterator# ((aget ~async-iterable (.- js/Symbol asyncIterator)))
handle-next# (fn handle-next# [value#]
(let [next# (.next async-iterator#)]
(.then next#
(fn [res#]
(if (.- res# done)
value#
(handle-next (conj value#
(let [~binding (.- res# value)]
~@body))))))))]
(handle-next# [])))
@U3JH98J4R I should have clarified that I'm looking to consume AsyncIterable. I tried with
(defn ipfs-stat [ipfsPath]
(go
(<p! (.ls (:ipfs @app-state) ipfsPath))))
Thanks for the code sample though, it going in my rosetta notes for js and cljs 😉
but it returns error in browser console Uncaught TypeError: p.then is not a function at Object.cljs$core$async$interop$p__GT_c [as p__GT_c] (interop.cljs:19) at switch__30353__auto__ (core.cljs:20) at eval (core.cljs:19) at Function.ipfs_browser$core$ipfs_stat_$statemachine__30354__auto____1 [as cljs$core$IFn$_invoke$arity$1] (core.cljs:19) at Object.cljs$core$async$impl$ioc_helpers$run_state_machine [as run_state_machine] (ioc_helpers.cljs:43) at Object.cljs$core$async$impl$ioc_helpers$run_state_machine_wrapped [as run_state_machine_wrapped] (ioc_helpers.cljs:47) at eval (core.cljs:19) at cljs$core$async$impl$dispatch$process_messages (dispatch.cljs:27) at MessagePort.channel.port1.onmessage (nexttick.js:214) @UJ1339K2B Can you please give an short example.
FYI, I'm using IPFS API, here are https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfslsipfspath on the ls method with example in Js
I'm getting the feeling I probably not using <p! right it comes from this require statement - [cljs.core.async.interop :refer-macros [<p!]]
I think that for async...of
does not translate to anything - it's just new syntax. I don't think there's a way to make it work with CLJS yet...
Maybe use the (js* "javascript-code-here")
to somehow handle this problem?
@U3Y18N0UC using .next
seems to working, it returns promise one by one. thanks for the js*
trick, I didn't know something like this also exists
@nikwarke I posted a more "complete" code sample with explanation on your ask.clojure question https://ask.clojure.org/index.php/10896/how-to-work-with-asynciterable-interface-in-cljs
@nikwarke yes, the .next
works but only for asyncIterators
. There's also asyncIterable
(notice the able
in the end) and to make that work, we have to use the js*
macro.
@U3JH98J4R unfortunately, the code you posted on ask.clojure will not work - that was my first attempt. aget
only works for string arguments - but you can replace that code with (js* "~{}[Symbol.asyncIterator]" async-iterable)
and then it works 🙂
Yeah, I agree 😅
im also pretty sure what i wrote would eventually blow the stack, but idk a better way
Hmm, while using .next
I started getting another error. It was related to parsing a JS object to cljs and I wasn't sure who is responsible, cljs or ipfs api. I started reading code from ipfs side. I'll give it try with *js
as well
@nikwarke there's no explicit support for it - but I think the idiomatic thing to do would be to turn into a streaming channel
@dnolen I think it does convert to channel. I tried to run following code
(go (let [val (<! (.ls (:ipfs @app-state) addr))] (println val)))
which returned - #object[cljs.core.async.impl.channels.ManyToManyChannel]
Here are the docs for the 'ls' method of IPFS api that I'm using - https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md#ipfslsipfspath
@U287L02DT Aha! yes, it returns - #object[Promise [object Promise]]
Cool, so you can process that AsyncIterator via a ClojureScript (loop [] (recur ...))
+ some Promise interop. Not the prettiest thing, but it seems like it should work.
Basically, keep calling (.next …) until the iterator is exhausted. For every promise you get, I would put the Promise return value onto a core.async channel (or just for debugging, you can swap!
to an atom - not a good approach in general)
@dnolen while this solution works for AsyncIterable
, it does not work for AsyncIterator
. So, there's no way to consume this, for example, in ClojureScript:
(js* "myAsyncIterable = {
async* [Symbol.asyncIterator]() {
yield 1;
yield 2;
yield 3;
}
};
")
Do you have any idea on how to make this work? Is it something that'll need to be added to the ClojureScript compiler?Ok, I found a way :). Will try to summarize in a gist so other people can use it too
then you can use all normal ClojureScript iteration patterns as well as core.async
streaming channel ops
that said, would be yet another nice community contribution - I helped guide the other JS interop stuff - happy to guide this one too
I would say the difficulty is pretty low - provided you understand the JS and CLJS sides of the problem
We are seeing some seemingly odd behavior using def
where we are memoizing a more expensive function for caching in our Lambda (using ShadowCLJS), and doing a with-redef
When testing with the Spy library to mock it, we get the error:
#error {:message "Promise error", :data {:error :promise-error}, :cause {:status :unexpected-failure, :body #object[TypeError TypeError: user_auth_profile.employee_record.update.build_client is not a function]}}
Looking in deeper, it also happens if we do a with-meta
as well, but if we do a let
over the def
we can do the with-redef
fine.
I have tried to create a simpler version of the flow to try to reproduce it, but the simple example doesn’t reproduce like the other one, which I will paste in the thread…The sample src (although this works, but the structure is the same)
(def build-client (memoize identity))
(defn- process
[event]
(async-utils/go?
(let [client (build-client {:some :settings})]
(assoc event :client client))))
(defn lambda-handler
[event]
(let [p (p/promise)]
(async/go
(try
(->> (let [res (async/<! (process event))]
{:success res})
(p/resolve! p))
(catch :default e
(p/reject! p e))))
p))
the sample test:
(deftest test-lambda-handler
(testing "It adds the client to the event"
(async
done
(go
(println "calling simple repro test with build-client as spy ")
(let [build-client (spy/mock (fn [_config] {:double-impl :results}))]
(with-redefs [er-update/build-client build-client]
(let [res (core.async.interop/p->c (er-update/lambda-handler {:test :event}))]
(is ((complement nil?) res)))
(done)))))))
still playing with it, and we have some work-arounds, but it has piqued curiosity on if there are any complier tricks or something that may account for the differences where a MetaFn
cannot be called, but a Function
can be called
and that in the scenario when it fails, e.g. (build-client {})
if I change it to be an apply, e.g. (apply build-client {})
the apply works
since you haven't posted the non-working code it's hard to diagnose why that is occuring
it sounds like there are two issues:
• with-redefs
isn't behaving the way you assume in async code
• an error is occurring when passing a MetaFn
to a promise
> but if we do a `let` over the `def` we can do the `with-redef` fine. what does this mean?
the first fails:
(def build-client (memoize identity))
but if I do a let
to capture the memoize and then delegate to using a function, it works:
(let [build-client* (memoize identity)]
(defn build-client
[config]
(build-client* config)))
so it made me think there was something about the def
itself, but that might be a wrong hypothesis
and there are other mocked out functions (defined with defn
) that are in the with-redefs
block that work, where the item setup as a def
doesn’t
if I do a with-redef
on the var and give it a Function
instead of a MetaFn
it works as well
which is what was leading me to think there might be some nuance around the def
when not augmented in the output of defn
macro transformation
it sounds like the issue is memoize
creates a metafn (not sure) and you're trying to use it with a promise
with-redefs
is a massive footgun if you're doing anything async. I would not recommend it
testing out removing the with-redef
by giving a dumb implementation that we don’t have to mock out now though
based on your example code (That does work as you said) I cannot see what changes you would need to make to break it in the way you're seeing
the error sounds like it's something akin to
Promise.then(build_client)
but that doesn't seem to occur in your codeso:
• taking out the with-redefs
and giving an identity
in the source (which we don’t use because the part that consumes it is mocked out as well), works
• wrapping the call with with-meta
in the source (e.g. ((with-meta build-client {:meta :data}) config}
and no with-redefs
works
• passing a spy/mock
for build-client
by using with-redefs
and having that wrapped in the with-meta
call above works
• removing the with-meta
around the invocation works
• changing it to (apply build-client config)
works regardless of MetaFn
or not, as well as with-redefs
Hello there,
I'm a reagent beginner and I'm stuck on a (seemingly) simple problem in form fields handling.
I don't want to use reagent/forms for now, in order to understand how the "vanilla" system works.
Here is the thing: I want to bind an atom to a checkbox (easy, isn't it ?:)
But when I click my checkbox, the state doesn't change.
I wrapped up the atom in the following component for the demo :
(defn my-checkbox []
(let [state (r/atom true)]
[:input {:type "checkbox"
:checked @state
:on-change (fn [e]
(let [v (-> e .-target .-value)]
reset! state (not v)))}]))
I tried to trace the value of v, it's correct.
Could you please guide me to the right path? :)
Thanks a lot !
Replace let
with reagent.core/with-let
, according to your import of reagent.core
.
Wrap reset!
and its arguments in ()
.
And finally, definitely go through Reagent examples and documentation - there's not too much of it, and it explains a lot.
Also, there's #reagent
One other problem - you need to use the checked
property, not the value
for that HTML control. So (.. e -target -checked)
is the value you want to reset!
your reagent atom to.
Hi, so after some head scratching here is a working component
(defn my-checkbox []
(let [state (r/atom true)]
(fn []
[:input {:type "checkbox"
:checked @state
:on-change (fn [e]
(let [v (-> e .-target .-checked)]
(reset! state v)))}])))
@U2FRKM4TW I tried the with-let without success, I'll investigate further 🙂
@U08JKUHA9 the checked property was important, thank you !
And the two other points were to
• Return a function instead of the hiccup stuff so that the atom is not reinitialized at component refresh
• reset to (-> e .-target .-checked) instead of its negation because the checked property was already toggled in this handler !
Thank you so much to you both :)So does it not work if you write it like this?
(defn my-checkbox []
(r/with-let [state (r/atom true)]
[:input {:type "checkbox"
:checked @state
:on-change (fn [^js e]
(let [v (-> e .-target .-checked)]
(reset! state v)))}]))
Hi everyone, I just watched a https://m.youtube.com/watch?v=3HxVMGaiZbc from David Nolen and had a couple questions:
1. He mentions that all components are in Javascript and they get called in CLJS with the appropriate props. How does he do that?
2. At roughly 29:00 we can see a bit of their code and I noticed that their view
function returns another function that accepts props. What is the benefit of this pattern?
3. Also at 29:00 we see that they seem to pass around an increasingly large props
map by merging new keys into it. What's the benefit of that pattern?
Thanks in advance! This was a great talk. I can't believe their CLJS codebase is only 1500 LOC...
i’m not part of their team, but assuming Reagent is what they’re using:
1. You can use JS packages inside of CLJS (interop). So, they likely write React components, make it available to their project as a library and just use them directly. Think material-ui. see https://github.com/reagent-project/reagent/blob/master/doc/InteropWithReact.md
2. That appears to be a form-2
reagent component.
3. This can be done for many reasons, but often it’s chosen for convenience (they’re chosen might be for a different reason though!)
Again, i’m not speaking for them, just how I interpret the code.
On 3: I think I use this or a similar pattern sometimes, not just in frontend code. If you have some process that can be described as a pipeline (might be tree-like), then you can either encode it like so: A -> B -> C Where each arrow is a full transformation to a different data structure. However you can encode it like so too: (A) -> (AB) -> (ABC) Here, information is not lost between transformations, because they just add things. The parens imply that you wrap the data structure into something. Destructuring, pattern matching, multimethods or other forms of dispatch can then be used to narrow down what any particular function cares about. Note that (ABC) might make the most sense if A, B and C are structurally equivalent, so you can combine them more deeply, it’s not necessary but it would bring unique advantages. (ABC) might also be a denormalized structure, meaning parts of it are derived from other parts. What’s the advantage of this? * It’s for example nice if you didn’t quite figure out what your modules/functions need to know. * It’s great for observing and debugging. * The stuff that consumes your transformations in the end might have special dispatch rules much later in the development to account for “special cases”. * Metadata might be a better solution for this in some cases.
Thank you @U6GNVEWQG!
1. Ah I guess I could export
the JSX components and use them in CLJS like [:> FlipMove ... ]
2. Oops of course 🙂
3. They mention that all their components can be simulated in Storybook so I guess that means they rely heavily on props. Does that mean their entire UI re-renders constantly whenever anything in the props changes?
> so I guess that means they rely heavily on props Their components are, as David notes at one point “static”. In the react world, they use the word “dumb components”. This is a good thing 🙂 You will strive to make as many components as possible “static” > Does that mean their entire UI re-renders constantly whenever anything in the props changes? I can’t speak for vouchio, but I doubt they would have excessive re-renders. In general, components don’t re-render unless props change. You get to control when props change and which components are listening so you can control when re-renders happen.
Honestly, their approach is pretty great and I use a similar approach in my apps as well. The only difference is that I write my components in CLJS. Now, keep in mind, if your building a team of JS devs and moving them to CLJS, Nolen’s approach seems like a good one.
@U6GNVEWQG on closer inspection, is it a form-2 component if the outer function and inner function don't share the same arguments? Or am I just extremely ignorant about form-2 components?
I would have to double check, but all my comments are based on the assumption they are using Reagent, but who knows, maybe not. Nolen said it’s just an HOC so maybe this isn’t. I wouldn’t get too hung up on it though 😉
@ronny463 1. interop w/ ClojureScript just works 2. just higher order stuff 3. the underlying component just does a lot of stuff (manages many different nested views) - we don't care about the details but event handlers for the various things must be setup
For 3. do you find that a lot of unaffected parts of your UI are re-rendering because a small part of the props map is updating?
Can I also ask where you subscribe to the reframe events if the components rely entirely on props? I guess that's done when the app is initialized and the results are passed down as props?
props are for the pure components, business logic state changes are handled by re-frame
note it would be a lot more code but by pushing all the components, styling etc. to JS/Storybook - then nothing is left for the ClojureScript except business logic
I also like that it allows you to bring on JS developers and have them be productive right away
What are some storybook like libraries in the cljs ecosystem? I know of dynadoc, devcards, nubanks worspaces, any others people have seen or used?
@dnolen with that approach, is needing to convert back and forth from js to clojure data types (like maps, vectors, keywords) constantly a problem?