Fork me on GitHub
#clojurescript
<
2018-03-31
>
kvlt01:03:53

Could one, using cljc, perform a datomic query from a cljs file?

robert-stuttaford07:03:03

@petr you can put clj code in a cljc, protected by ?#(:clj …) wrapper, but that wouldn’t magically make it so that your browser app could call it. you still need to take care of all the networking stuff.

danielo51508:03:44

One question, it's about transit

danielo51508:03:26

If it converts the JSON to structures where all keys are strings... what's the advantage over raw object access ?

danielo51509:03:00

Maybe because vectors are properly converted?

alexmiller11:03:55

It caches commonly used keys so if you pass a bunch of maps that share keys you are sending far less data

danielo51512:03:37

That is for talking to a tansit remote

danielo51512:03:57

I am talking about reading a JSON response

danielo51512:03:05

Can somebody explain me why is this putting nil on the channel ?

(defn handler [ch msg] 
  (fn [err res body]
   (go 
      (if err 
        (js/console.error (str "Bad error during " msg) err)
        (>! ch (t/read r body)))
      (close! ch))))
If I add an extra (prn (t/read r body)) inside this function I can see the correctly parsed object, but outside it's still nil. I'm a bit puzzled

alexmiller12:03:52

Should r be res?

danielo51513:03:45

nope, sorry for the lack of context

danielo51513:03:51

r is a json reader

danielo51513:03:59

(t/reader r :json)

danielo51513:03:41

One small question, will a go block wait for all the take instructions ? Even nested ones ?

justinlee15:03:06

@rdanielo what do you mean by nested?

justinlee15:03:49

the go block will create code that appears to wait for for any <! in its scope. note that it can’t see past function boundaries

danielo51517:03:24

Here is an example code that I'm not sure if it works properly

(defn finishUpgrade [service]
  (go 
    (if-let [finishUrl (get-in service [:actions :finishupgrade])] 
      (do (prn (str "About to finish upgrade for " (:name service)))
          ((<!(Post finishUrl))))
      (prn (str "Finish upgrade not required for " (:name service))))
    service))

danielo51517:03:47

Well, I know it only works when the Post is not executed

alex-dixon16:03:10

Does anyone know how to set up figwheel development for a SPA with no hashes in the client side routing? I’ve looked all over, tried a couple approaches but I can’t seem to serve the index.html page when I have a path with more than one slash in it. I.e. /user works, /user/, /user/foo don’t. I’m getting that behavior using this: https://github.com/mhuebert/figwheel-pushstate-server/blob/master/src/figwheel_server/core.clj and https://github.com/quangv/re-frame-html5-routing/blob/master/src/clj/my_app/dev_server.clj as the :ring-handler option to figwheel in project.clj

justinlee16:03:40

@alex-dixon you mean the issue is that it won’t reload the page when you are in /user/foo?

alex-dixon16:03:36

Client has routes /about and /dashboard, but when I go to /about/ or /about/anything-else I get

Navigated to 
app.js:1 Uncaught SyntaxError: Unexpected token <
foo:11 Uncaught ReferenceError: my_app is not defined
    at foo:11

justinlee16:03:37

@alex-dixon yea but i still don’t know exactly what you mean when you say it doesn’t work. you mean figwheel won’t autoreload or you mean when you manually refresh it doesn’t serve index.html

justinlee16:03:53

so it seems like problem one is that your client side routing is messed up because it shouldn’t be making a server call when you navigate there

justinlee17:03:25

but that error message is what happens when the server side isn’t serving index.html. i’ve gotten that before too

alex-dixon17:03:52

Understood. But I need the client in charge of everything, so when I navigate to /user/foo I want the server to just defer to the client app (serve “index.html”) and have the client pick up from there

justinlee17:03:17

what happen when you hit reload in the browser after getting that message without changing urls?

alex-dixon17:03:36

Same thing. The error above is generated by typing in the URL and pressing enter

justinlee17:03:06

but not when you just try to hit /about

danielo51517:03:24

Here is an example code that I'm not sure if it works properly

(defn finishUpgrade [service]
  (go 
    (if-let [finishUrl (get-in service [:actions :finishupgrade])] 
      (do (prn (str "About to finish upgrade for " (:name service)))
          ((<!(Post finishUrl))))
      (prn (str "Finish upgrade not required for " (:name service))))
    service))

justinlee17:03:43

@rdanielo this: ((<!(Post finishUrl))) isn’t right. you need (<! (Post finishUrl))

alex-dixon17:03:27

Ah hah….seems like a problem with no slashes in :asset-path config and the index.html file. Think I got it @lee.justin.m thanks for helping 🙂

justinlee17:03:28

the way you wrote it you’re invoking the response from Post as a function, which also means that the go block won’t wait on the take since its inside a function

justinlee17:03:15

I really dislike that fact that that error message is so bad and that logging is off on the server side by default

justinlee17:03:32

i had this same issue before and it was such a pain to debug. now that you mention it, it was something about relative paths

alex-dixon17:03:32

Yeah lol…so frustrating. Going to see if I can pr to that repo in hopes of having better CLJS examples for JS devs. Seems to work perfectly now. Thanks for your help really appreciate it

justinlee17:03:42

i really want to understand why it is interpreting html as javascript but that’s for another day

danielo51517:03:31

@lee.justin.m I think you are right... Previously there was a prn statement there, and I forgot to remove the paren

danielo51517:03:35

Let me check...

justinlee17:03:13

if you wrap a <! in a prn statement I think it will actually screw with the go block, by the way. you need to let-assign the value and then prn it

danielo51517:03:36

Buff, that could become a nested nightmare really fast

danielo51517:03:51

Even If I want to discard the result and just wait for the response ?

justinlee17:03:34

you have to keep in mind that the go block is a macro that does static analysis on your code. it will look for <! and then transform your code into something else. but it will not cross function boundaries, although it is fine if you have a macro like when. i probably don’t understand some of the subtleties but that’s how i understand it

darwin17:03:56

yep, could be a bisecting nightmare, there are some sharp edges when using go blocks in clojurescript, for example having (set! (.-prop obj) "something") in the go block scope will produce wrong code with hard-to-understand errors, in my case set! form was expanded from a macro used in a go block, so it took me few hours to figure out what is really going on there, the best approach IMO is to minimize your go block code to absolutely barebone logic and delegate all non-async stuff into separate functions

darwin17:03:22

also some nitpicking-notes about the code posted above: 1) use if-some instead of if-let 2) clojure/lisp convention is to use kebab-case names for functions and local names

justinlee17:03:18

on the off chance that you are a javascript programmer and have ever messed with async/await, it’s the same thing. async does not cross function boundaries, so you have to rewrite those functions to return promises so you can wait on them.

darwin17:03:40

ah, actually scratch the first note, I thought you are taking from a channel there

danielo51517:03:06

Thanks to both @lee.justin.m for the explanation, I have the same perception as you described. And thanks @darwin for pointing those details, I am a node developer and I tend to go for camel case names

danielo51517:03:40

About the if-some vs if-let ... should I make the let and then the if separately ?

justinlee17:03:50

as someone who does a lot of js/cljs interop, you’ll be much happier if you use kebab case for cljs and camel case for js. it really helps to keep track of what the hell is going on.

danielo51517:03:58

Yes, I am talking from a channel

danielo51517:03:30

> it really helps to keep track of what the hell is going on. That makes a lot of sense

justinlee17:03:57

I often have camel case in my code, but it always means, “this is a javascript object”

darwin17:03:00

you should always write (if-some [thing (<! channel)] <code>)

danielo51517:03:24

the finish url is not from a channel

darwin17:03:59

the reason is: channel yields nil when closed, if-let would go to else branch when nil or false

darwin17:03:30

in general there may be good cases to put false on the channel as ordinary value (no closing scenario)

darwin17:03:13

it is subtle, but better to think about this, if you later decided to put false on some channels, all the places where you if-let take from those channels would break

darwin17:03:01

also when writing core.async heavy code I would recommend to adopt naming convention where all functions which return channels are prefixed with go- and functions which would return go channel only in some cases are forbidden - this makes reading and reasoning about such code much more paletable

justinlee17:03:11

hm that’s a good idea

darwin17:03:45

I went as far as wrapping core.async with my own macros to have that convention “everywhere”, e.g. timeout becomes go-wait

darwin17:03:11

later I will add there some poor-man’s detection of some go’s gotchas, e.g. detecting that set! usage should be pretty easy check in my wrapping go macro

justinlee17:03:07

would it be possible for the go macro to warn right away if it sees a <! behind a function?

darwin17:03:36

not sure, that seems like a more difficult analysis

justinlee17:03:50

it already knows where the boundaries are, so why not just keep moving down the tree?

danielo51517:03:11

Thanks for all your tips @darwin

danielo51517:03:56

However the code I posted is a bit subtle to what you are describing. Let me explain what it should do and then you explain me how do you think it's better

danielo51517:03:57

The function takes an object and returns a channel (that's what the go block allways do right?) I want the function to always return the same thing, the object it gets. If the object contains certain property, then execute a remote call and wait for the response , if not just do nothing about it and also return the same object again to the channel. This will be fairly easy with promises, but I'm not sure if it may be a good practice with channels.

danielo51517:03:57

I think it is important that it allways executes asynchronously, I mean for the two possible branches, and I'm not sure if it works this way with channels

darwin18:03:52

yes, IMO it is fine to use ad-hoc channels to pass just one value asynchronously (they work as promises in this scenario) and the other point about all branches return some channel is also reasonable thing to do, functions should either return a channel all the times or never, mixed functions are quite hard to reason about (when you revisit the code later)

wilkerlucio18:03:02

@rdanielo sorry, getting later on the boat so I might miss context, but had you tried using the promise-chan? it has the same behavior as a promise, it will return the same value every time after its resolved

justinlee18:03:36

though at some point, one wonders, why mess with channels at all? just use promises

wilkerlucio18:03:27

@lee.justin.m if you are in cljs land, you want the go block to have an async/await style coding, otherwise you have to keep doing callbacks

justinlee18:03:08

right, and that’s why i started using go blocks, but I wonder if it is worth it.

wilkerlucio18:03:30

for dealing with js promises I have a macro helper that's very handy IMO:

wilkerlucio18:03:42

#?(:cljs
   (defn promise->chan [p]
     (let [c (async/promise-chan)]
       (.then p
         #(async/put! c {:success %})
         #(async/put! c {:error %}))
       c)))

#?(:cljs
   (defn consumer-pair [resp]
     (if (contains? resp :error)
       (throw (:error resp))
       (:success resp))))

(defmacro <!p [promise]
  `(consumer-pair (async/<! (promise->chan ~promise))))

wilkerlucio18:03:01

with this, you can use <!p inside go blocks to read promises directly, like:

wilkerlucio18:03:43

(go
  (let [json (-> (js/fetch "some-url") <!p .json <!p)]
    (js/console.log "response" json)))

wilkerlucio18:03:00

I think totally worth it 🙂

👌 8
justinlee18:03:16

hm that’s fun

justinlee18:03:50

the thing i grapple with is how to ensure i deal with unexpected exception correctly with channels. promises interact with exceptions in a much easier to reason about way

wilkerlucio18:03:12

(defmacro go-catch [& body]
  `(async/go
     (try
       [email protected]
       (catch :default e# e#))))

wilkerlucio18:03:18

this helps ^^

wilkerlucio18:03:30

as you can see, the <!p will throw an exception when the promise fails

justinlee18:03:40

it’s nice, but what happens when you start using things like async/map?

wilkerlucio18:03:28

you mean from core async?

justinlee18:03:52

on #core-async someone told me that i should be using transducers instead of map

justinlee18:03:21

this is the kind of complexity that makes me uncomfortable with this stuff

wilkerlucio18:03:28

but if you think on map, on a core.async fashion, a lot of times you want to make it parallel

wilkerlucio18:03:52

then you can do pipeline-async, which is more code but has a very efficient result

justinlee18:03:20

maybe the answer is: just use go-catch and <! and everything will be okay

wilkerlucio18:03:36

yeah, prettty much, but instead of <! I do <?

wilkerlucio18:03:45

(defn error? [err]
  #?(:clj (instance? Throwable err)
     :cljs (instance? js/Error err)))

(defn throw-err [x]
  (if (error? x)
    (throw x)
    x))

(defmacro <? [ch]
  `(throw-err (async/<! ~ch)))

wilkerlucio18:03:55

because if a channel returns on an exception, this will keep the propagation

justinlee18:03:55

nothing i do is high performance. i just want small debuggable code

justinlee18:03:07

with good ergonomics and readability

wilkerlucio18:03:27

I was worried about performance for a while on this too, but I have been doing some aggressive usages of this, and I'm not seeing performance been an issue

wilkerlucio18:03:49

creating and disposing channels seems a very lightweight operation

justinlee18:03:49

it’s a network call already so it’s not going to matter for me

danielo51520:03:22

Usually the performance on async operations is not so important since much of the computation/waiting will happen on the remote side and there is nothing you can do about

danielo51520:03:37

However error propagation is something that worries me a lot

danielo51520:03:22

I'm not sure about this but all the code you posted seems to just throw the errors happily

danielo51520:03:42

I don't want my process to crash because an http call have failed

justinlee21:03:55

this stuff works well so don’t worry too much about it, but there’s this one bug in cljs-http that really bothers me. if you get bad json from the server, cljs-http will print an uncaught exception to the console and you won’t have a coherent stack trace, because core.async uses nextTick, which doesn’t give you async call stacks. and the go block won’t return, so you can’t deal with it from the consumer side. i have a patch in mind for the library, but the whole thing makes me uncomfortable with core.async

😥 4
jrbrodie7721:03:35

Has anyone seen cases where lein/figwheel doesn’t pull down dependencies? I keep getting “no such namespace cljsjs.chartjs” but I’ve checked that it’s in my project.clj and done several cleans etc. any debugging tips would be great. (FIXED: I deleted everything under target/ and resources/js and that seemed to fix it.

darwin21:03:13

@lee.justin.m you could work-around it in chrome with cljs-devtool’s async feature: https://github.com/binaryage/cljs-devtools/blob/master/docs/faq.md#what-is-the-async-feature it was recently fixed in Chrome, so with Chrome Canary you don’t even need to use that hack anymore and you get better stack traces

justinlee21:03:14

oh that’s good news! thanks @darwin