I see. I wasn't clear that I want to pass the def's symbol as one of the arguments to macro. I'm trying to write a macro that will build a query in compile time and trying to pass some meta information like names of collections, server, etc.
Meta information is constant (doesn't change in runtime) and I'm trying to figure out if there is a way to access it from macro
Ah, then it's @(resolve symb)
. But the comment about it being in a CLJC (or regular CLJ) file still applies.
symb
has to be a fully qualified symbol, of course.
Thanks, it works with cljc file.
How might I change this function so that I get a js-object in my repl as opposed to this promise? I have no idea how javascript really works, I just thought I could slap a few functions together.
You cannot get unwrapped result of the promise that way.
Ok, is there any way to avoid promises altogether? I really don’t want to overcomplicate fetching json in the browser.
Or any other way rather than:
(-> (get-json all-posts-url)
(.then (fn [posts] (def all-posts posts)))
It's not over-complication. It's how Javascript works and I would rather stick to promise, promesa. Alternatively you can use Clojurescript channels.
But still the result of fetching will be a channel and not a plain value.
You know, I’m not attached to using js/fetch
. Is there a library that abstracts this away so I can just do something like (slurp url)
. Maybe a bit naive, but I figured I’d ask anyway.
Honestly if there is no abstraction already I’m just gonna make my own, because I’m not too sure it’s worth learning what promises are just for getting a few lines of json.
that is technically impossible in JS. all IO is async and there is no way to get a sync result from that.
Awww man. Well then I guess I’m learning how asynchronous stuff works.
I mean in certain situations it's possible when using Node.js Fibers. Caveats are: • it's experimental • works only in Node.js Your best bet is to learn the asynchronous nature of Javascript :)
node does have some special sync methods but yes those do not exist in the browser
I’m gonna look into using https://github.com/clojure/core.async then. Seems to me that this is the more idiomatic way of doing async programming in clojure/script.
if its just one function core.async is probably overkill but it is certainly a good option
Core.async is overkill for almost every situation in the browser environment.
I’ve never done any asynchronous programming before, so maybe this will be easier to learn.
it'll be much harder learning core.async and using it correctly than just a promise with a .then
Using core async you can actually have composable backpressure, and when you want to do composable state management where the state in question comes from remote/async io then what would you use? Any reasonable js app will have such situations. The fact that you can use go blocks and have your data written as if it was sync, not async, kinda matters. Yes, if you squint and ignore all the design issues with the javascript Promise API, then that also looks a bit like sync ordering, but it's not even comparable to CSP. I used js-csp and other csp libs in js since 2015 because it's just so much easier to maintain and develop with it than with any other async "solution".
well worth learning it IMHO but still something to be aware off 😉
CSP is just algebra, you learn the algebra and you are fine ☺️
I don't get what is the issue with PromiseAPI in Javascript world, could you please elaborate @ashnur?
It has a history that I have experienced first hand, even before https://promisesaplus.com/ became a thing
.then is overloaded, it does both map and flatmap, I don't consider that a good idea until recently it was relatively difficult to cancel promises and many js devs don't even know how serious that issue is there is also an alternate perspective, not mine, that criticizes it differently https://avaq.medium.com/broken-promises-2ae92780f33
The one downside of core.async is that the output code is somewhat larger, esp when using (go …) blocks. Otherwise, the paradigm is solid and has been very useful to me in many use cases, esp. in the browser/JS world.
A well written async flow with CSP can be modified at any place, can be extended anywhere, and I would say it's both simple and easy compared to what you get with Promises.
I would very much agree with @ashnur here
: ) Thanks, I can also agree that it can be overkill, but on the other hand, what is the production size difference if you do not use core.async?
Quite huge 😄 to be honest.
I'm developing quite important and big e-commerce site which extensively uses core.async (mostly for fetching). Full core async + the code it generates takes ~460kb. This is the only issue I have with core.async.
I am genuinely interested, because before cljs, when I had to support old browsers, the js build was the same size as cljs with core async
Another benefit of core.async is that it will work as far back as IE6, if I’m not mistaken.
(I mostly use it for ReactNative, so the output difference is not a big deal for me) My CLJS output at the moment is 1.5MB. The app itself is 30MB+.
460kb full application doesn't seem to be much, probably because I am used to material-ui bloating my build
Because of some early decisions we are also using fibers and async context. Core async without patching leaks the context, so we had an issue in which session would jump between the users.
460kb is the size of core.async + code it generates in the application.
Full application (together with lazy modules) is around 6mb.
@karol.wojcik what are fibers/async context?
• https://www.npmjs.com/package/fibers • https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage
Oh… You’re using node?
Unfortunately.
Sorry, zero experience there 🙂
No worries 😄 I have plenty experience with Node.js that's why I know that fibers + core.async + asynchrounous context is a bad design choice 😄
So is that a problem with core.async that appears specifically on node? Or is the problem in node itself?
Everything I was saying, I was referring to client-side core.async (browser and/or ReactNative). No experience with core.async on the server (node).
Clojurescript backend?
I have used core.async on server, but only Clojure JVM.
The problem is that if you master/worker architecture and you use core.async in fibers to populate an asynchronous context, then you can be pretty sure that the little functions which are made by core.async
are not wrapped in fiber
and as a result you have a leaking context.
Sounds complex 🙂
It's complex. Nowadays I do avoid core.async even for simpler stuff.
Rrright. But that sounds like a problem that’s specific to node + core.async situation.
The problem with build size which I mentioned earlier on is real 😄
You can see how much code core.async generates under the hood.
It's 2x/3x code generated by async/await.
Just (set! print-fn-bodies true) and evaluate a (go …) in the REPL
😜
(not sure if that would shrink after :advanced though)
Exactly 😄 The most of the code in the application is fetching. I have like 100 functions which fetch something and all of those use core.async. Of course that for 1-10 functions difference is negligible, but scale matters.
(set! print-fn-bodies true) true ar.main=> (fn [] (a/go (+ 1 1)))
(require '[cljs.core.async])
(cljs.core.async/go (<! (cljs.core.async/chan 1)))
var c__9__auto___47 = cljs.core.async.chan((1));
cljs.core.async.impl.dispatch.run(((function (c__9__auto___47){
return (function (){
var f__10__auto__ = (function (){var switch__4__auto__ = ((function (c__9__auto___47){
return (function (state_40){
var state_val_41 = (state_40[(1)]);
if((state_val_41 === (1))){
var inst_36 = cljs.core.async.chan((1));
var state_40__$1 = state_40;
return cljs.core.async.impl.ioc_helpers.take_BANG_(state_40__$1,(2),inst_36);
} else {
if((state_val_41 === (2))){
var inst_38 = (state_40[(2)]);
var state_40__$1 = state_40;
return cljs.core.async.impl.ioc_helpers.return_chan(state_40__$1,inst_38);
} else {
return null;
}
}
});})(c__9__auto___47))
;
return ((function (switch__4__auto__,c__9__auto___47){
return (function() {
var cljs$user$state_machine__5__auto__ = null;
var cljs$user$state_machine__5__auto____0 = (function (){
var statearr_42 = [null,null,null,null,null,null,null];
(statearr_42[(0)] = cljs$user$state_machine__5__auto__);
(statearr_42[(1)] = (1));
return statearr_42;
});
var cljs$user$state_machine__5__auto____1 = (function (state_40){
while(true){
var ret_value__6__auto__ = (function (){try{while(true){
var result__7__auto__ = switch__4__auto__.call(null,state_40);
if(cljs.core.keyword_identical_QMARK_.call(null,result__7__auto__,new cljs.core.Keyword(null,"recur","recur",(-437573268)))){
continue;
} else {
return result__7__auto__;
}
break;
}
}catch (e43){var ex__8__auto__ = e43;
var statearr_44_48 = state_40;
(statearr_44_48[(2)] = ex__8__auto__);
if(cljs.core.seq.call(null,(state_40[(4)]))){
var statearr_45_49 = state_40;
(statearr_45_49[(1)] = cljs.core.first.call(null,(state_40[(4)])));
} else {
throw ex__8__auto__;
}
return new cljs.core.Keyword(null,"recur","recur",(-437573268));
}})();
if(cljs.core.keyword_identical_QMARK_.call(null,ret_value__6__auto__,new cljs.core.Keyword(null,"recur","recur",(-437573268)))){
var G__50 = state_40;
state_40 = G__50;
continue;
} else {
return ret_value__6__auto__;
}
break;
}
});
cljs$user$state_machine__5__auto__ = function(state_40){
switch(arguments.length){
case 0:
return cljs$user$state_machine__5__auto____0.call(this);
case 1:
return cljs$user$state_machine__5__auto____1.call(this,state_40);
}
throw(new Error('Invalid arity: ' + arguments.length));
};
cljs$user$state_machine__5__auto__.cljs$core$IFn$_invoke$arity$0 = cljs$user$state_machine__5__auto____0;
cljs$user$state_machine__5__auto__.cljs$core$IFn$_invoke$arity$1 = cljs$user$state_machine__5__auto____1;
return cljs$user$state_machine__5__auto__;
})()
;})(switch__4__auto__,c__9__auto___47))
})();
var state__11__auto__ = (function (){var statearr_46 = f__10__auto__.call(null);
(statearr_46[cljs.core.async.impl.ioc_helpers.USER_START_IDX] = c__9__auto___47);
return statearr_46;
})();
return cljs.core.async.impl.ioc_helpers.run_state_machine_wrapped(state__11__auto__);
});})(c__9__auto___47))
);
You can check this out: http://app.klipse.tech/This is mostly what we are doing. Where 1
is a call to function which returns channel.
I agree that if the task is easy, you can avoid core.async. But there are a few cases where it really shines, IMO. Fine grain animations that also need to be cancellable is one. Another is loading a lib that takes time to be “ready” and having a bunch of places in the code that need to wait for that event.
You do get a lot of value, but there’s a cost (big output size and some weird edge cases/bugs). I am quite happy that a few of the long time outstanding bugs got recently fixed on the CLJS side thanks to the latest CLJS fixes.
For example, (and …) (or …) were kinda broken inside (go …) loops but now work correctly and have proper termination semantics.
Man, why does this stuff have to be so complex? I just want to consume a url in the browser.
XD
🙂 Last example of something that would be, IMO pretty hard without core.async, but with it it looks easy.
@c.westrom don't get distracted by this discussion. you don't have to make it complicated. You already had all the code you need in the last snippet you posted. just process the all-posts
in the .then
callback instead of trying to put it into the def
I was not aware that node.js has issues with such build sizes
Yes, sorry @c.westrom 🙂 It was my bad.
and it does seem to be the exact situation which I am always afraid of, one thing in which we are overcommitted lead us down a path where each step is a reasonable choice at the time, but in the end we are doing something that if we knew ahead of time that it's a possibility, we could've chose a different path, possible one that overall better, even subjectively
When I was referring to build sizes I meant browser, sorry if it was not clear from my previous statements. We have an isomorphic application.
Ok, so just to recap, here’s my function.
(defn get-json
[url]
(->
(js/fetch url)
(.then (fn [res] (.json res)))
(.then (fn [json] (.stringify js/JSON json)))
(.catch (fn [err] (.log js/console err)))))
I don’t want to console.log the response, but rather have it show up in my repl, when I run (get-json url)
.@c.westrom I think the recap is that you… cannot. That would be a synchronous IO operation, and JS (at least in the browser) does not have that.
There’s no way at all to abstract this as a synchronous operation?
All you can do with that response is call another function or save it somewhere (like a CLJS atom). Saving it in an atom even temporarily can be useful for debugging and development in the REPL.
No. That’s a JS environment thing. CLJS can do nothing about that.
The “best” you can do is a core.async (go …) block 🙂 That gives you the illusion of synchrony. But it is an illusion. And we’re back to square 1 in this thread 😝
Or JS async/await (also an illusion of synchrony). But AFAIK, CLJS doesn’t really support async/await at the moment @thheller yes?
Ok, this data will be consumed by another function. Can I just call (get-json url)
and pass it as an argument?
I’m having a hard time reasoning about this. Would be nice to see what data comes out before I write a function to consume it.
^^ yes save it an atom and play around with it at the REPL. Do you have a functioning CLJS REPL setup?
Yeah I’ve got my repl. So I’ll create an atom and save the data there then… somehow. Let me try.
(defonce tmp-1 (atom nil) )
(defonce tmp-1 (atom nil))
(defn get-json
[url]
(->
(js/fetch url)
(.then (fn [res] (.json res)))
(.then (fn [json]
(let [json' (.stringify js/JSON json)]
(reset! tmp-1 json'))))
(.catch (fn [err] (.log js/console err)))))
Note that the above should be used only for debugging and not in any production application.
^^^ yes
see also my answer to this recently https://stackoverflow.com/a/67853185/8009006
Once you start using async stuff, you cannot get its results in a non-async context. You will have to make it async as well, in one way or another - core.async
, promises with callbacks, async/await, whatever. The only thing a sync function can do to an async one is just "fire and forget".
I mean, there’s a case for saving the resp in an atom for production, but overall I agree 🙂 The cases are few. This is an exploratory snippet/approach.
but yeah the temporary atom can make it work in the REPL
maybe I'll add support for some kind of await in the CLJS REPL at some point
but currently CLJS otherwise does not support async/await
I actually have never written any JS code with async/await. That’s also… an illusion of synchrony of some sort, right?
yeah, turns every place you use it into async. works pretty much exactly like the core.async go macro
just doesn't need to do all the rewriting since its supported natively
Is there anything like Next.js for ClojureScript SPAs? Hosting and SSR?
for future reference, I found this template for shadow cljs https://github.com/thheller/next-cljs
I haven't tested it yet, but maybe I will soon
Ok, evaluating @temp-1
:
Class: java.lang.String
Value: "{\"content\":\"<div><h1>Here's some stuff</h1><ul><li><p>Here's more stuff\\n</p></li><li><p>more stuff\\n</p></li></ul></div>\",\"data\":{\"title\":\"Lorem Ipsum\",\"subtitle\":\"stuff test\",\"date\":\"2021-04-15\",\"tags\":\"test\",\"author\":\"Christian Westrom\",\"id\":\"test-1\"}}"
or could I use Next.js for a ClojureScript app?
This is good, I think I’ll just make sure it’s an actual js object within get-json
.
For conversion to/from JS objects, https://github.com/mfikes/cljs-bean is a good choice.
js->clj
clj->js
Also work but cljs-bean lib is faster.Usually it doesn’t make a big difference, until it does.
Since I don’t need .stringify
couldn’t I refactor the original function like this?
(defn get-json
[url]
(->
(js/fetch url)
(.then (fn [res] (let [json' (.json res)]
(reset! temp-1 json'))))
(.catch (fn [err] (.log js/console err)))))
no. .json
returns another promise since its an async operation. so you need the (.then (fn [json] (reset! temp-1 json))
or (reset! temp-1 (js->clj json))
(defn get-json
[url]
(->
(js/fetch url)
(.then (fn [res] (.json res)))
(.then (fn [json] (reset! temp-1 json)))
(.catch (fn [err] (.log js/console err)))))
Ok, that seems to work.Ok, so since this is asynchronous, I basically have to chain my functions using (.then)
?
Or, in other words, pass functions into a (-> (.then (fn …)))
` ?
Yes. (.then …) takes a function which receives the result of the previous promise.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
How convoluted…
Async is not for the faint of heart, that’s for sure.
(any async: core.async, promises, etc)
lol, in actual existence, where anything that matters is, everything is async
Yes… and no. You can write PHP and barely touch any async 😃
(I came to Clojure from PHP around ~2014)
I have written php for many years
before js
Ok, if I’m not assigning this data to a variable, do I have to go to my endpoint and change it so it’s async?
but that's not what I meant. There are 3 main categories where things that can be named through language (human or machine): 1. our inner subjective experience which is primal, 2. language where we relate parts of our subjective experience and communicate it 3. the shared part of the communication which we assume is independent of the subjective and thus, objective. What matters is either in the 1. or 3. domains. What is in the 2nd domain, the interface layer, is the least important. Both 1 and 3 are async. 2. pretends that it's sync.
From what I can tell, next.js is a mix of things, but mainly it tries to make best-practices more accessible through some pre-compile steps. In ClojureScript, we have macros, so it would work more naturally AFAIK, but you would also likely not find a framework like next. You can maybe find libraries for the individual features though
I guess in nature, nothing is really synchronous, as far as we know (limited by the speed of light). That reminds of the Are We There Yet? talk by Rich Hickey. Perhaps moving into #off-topic 🙂
Alright, this has got to be bad style, but it’s gonna take awhile for me to really understand how this stuff really works.
if you are running code on a javascript vm, you have to know about the event loop
Ok, I have no idea what the event loop is.
when i do async stuff, even if I do it with promises, I try to organize them as smaller analogues to the event loop
probably just the name is new
What resources would you recommend I check out?
https://www.youtube.com/watch?v=8aGhZQkoFbQ this is browser, and if you do multithreading in node, it's different
for single threaded stuff, it should be more or less the same
https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
Curiously, I just learned the synchronous calls DO exist but are deprecated (and I don’t think anybody who’s serious about web/js dev uses them anymore): https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests
Might be a bit of a dumb question, but how do you interop js destructuring statement such as this one into ClojureScript?
const DefaultElement = props => {return <p {...props.attributes}>{props.children}</p>}
specifically <p {...props.attributes}>{props.children}</p>
in hiccup notation.
Ok, I just wanna really ground this in something I have working already. This is a stripped down version of my frontend.
(ns app.core
(:require [clojure.string]
[reagent.core :as r]
[reagent.dom :as r.dom]
[reitit.frontend :as rf]
[reitit.frontend.easy :as rfe]
[reitit.coercion.spec :as rss]
[app.data :refer [data blog-posts]]))
(defn blog-preview-page
[]
[:div {:class '[flex-grow]}
(for [blog-post blog-posts]
^{:key (-> blog-post :data :id)}
[blog-post-preview (:content blog-post)]
)
])
(defn app
[]
[:div {:class '[]}
[navbar]
(if @match
(let [view (:view (:data @match))]
[view @match])
(.log js/console
(str "Match not found.\n `(:data @match)`:"
(:data @match))))])
(defn mount-root
[component]
(r.dom/render component (.getElementById js/document "app")))
I have this reagent component blog-preview-page
. What do I have to do in order for it to interact with my asynchronous fetch?
Usually I would just call it as another value from app.data
or elsewhere.It seems like the solution will ultimately involve coupling fetch
with my frontend, I’d like to avoid that. So the other solution involves using atoms. Is this really it?
yes, you use an atom to store the result. where you run the fetch
depends on your app needs. you can just do it once on startup, or with some timer or other conditions
The key feature from Next.js I want is Server Side Rendering (SSR) and hosting. How do i get that in ClojureScript? I am aware that i could create my own node.js server, but it is just not as easy as with Next.js?
You just pass the attributes directly since Hiccup expects a map or a JS object (IIRC).
Since CLJS compiles to JS i can imagine that I could compile to a Next.js project, where I am using the Next.js Routing library instead of React Router. Who inside the clojurescript commnunity could know about this?
can you give me an example? @p-himik?
A translation to hiccup could look like the following:
(defn default-element [attributes & children]
[:p attributes (into [:<>] children)])
;; Usage as hiccup
[default-element {:style {:color :blue}}
[:p "I'm a child!"]
[:p "Another child!"]]
No need for the fragment. Just (into [:p attributes] children)
.
True! The fragment is there as a matter of opinion not requirement.
I haven't used next.js but unless they do some trickery with source code (like custom syntax, babel, etc.) you should be able to use it with cljs. You would probably need something like Hicada to build your React components, so there are no extra layers that can throw next.js optimizations off. Another way, is to use clojure back-end to generate HTML on server-side. Rum is an example, where they just use JVM to generate HTML from hiccup (I don't think hydration works in this way). Also, I think om/reagent can do something similar. I don't like either of those approaches and currently trying to sketch my own solution on top of firebase. CLJS offers some very cool capabilities with core.async and macros and I think eventually it can be better and simpler than next.
I had no problems running clojurescript code in firebase functions, so any nodejs webserver + hiccup should work with no problem (like express)
Is there any way to get something like (alias ) in clojurescript, without reqiuring the namespace? Mainly for using aliased namespaced keywords
Also, fulcro is an option but requires quite a lot of learning before you can get started.
I have circular dependencies for the specs namespaces, so cannot really require them
@bertofer not currently possible - circular deps won't build so I'm not sure what you are trying to do
Mainly for using ns-keywords aliased
as in alias
won't fix anything - it will just break
you should be able to use fully qualified name, but you should definitely resolve circular dependencies, as it will backfire even if it builds now.
you can not have a circularity in the graph ever
which isn't to say alias
wouldn't be useful - but it cannot be used to fix circular deps if they are submitted to the build
unless you're saying they aren't submitted to build
I mostly want to avoid using hardcoded long namespaces for keys, I don’t need to require the namespace. I have ns’s only for specs, and there I would like to have “circular” references, but only between specs
ah k - then no - not currently possible - but would be an useful patch - probably medium difficulty though
there is https://clojure.atlassian.net/browse/CLJ-2123. solution still pending for both CLJ and CLJS
thanks both, think I will look for a different approach for now and keep an eye on that issue, as in clj it’s also a bit “hacky” for the circular deps
we have a plan for this for Clojure 1.11
for CLJ-2123 that is, I haven't read all the backchat to know whether it would address the original question
You can simply (def my-kw :some.long.namespace/my-keyword)
as a workaround or even a proper solution.
that would work, though I prefer to use kw directly. For now I will go with the (alias create-ns) approach with reader macro for only clj, declare specs only in clj, then cljs can require those ns only for aliasing keys to access those fields in maps
@simon.el.nahas you can do serverside rendering with Rum
You have a .clj server, a .cljs SPA, and a .cljc file that hosts the shared components written in rum. You include all the shared components to the .clj and the .cljs, and the rendered artifact (html) must be identical to be able to up-strap the SSR with your SPA.
There are alternatives but I find rum to be the cleanest wrapper on react and the most straightforward way to achieve SSR.