I just had a wild (but maybe too magic) idea again. What if we coerced Atoms into Promises and compiled @ to just (cljs.core/deref ...) outside of async contexts and to js-await inside async contexts. Then we could write (let [resp @(js/fetch " when dealing with promises but also keep around compatibility with atoms
That's what I did in cljs-thread
Though probably not exactly like that
Well, it kinda worked for promises circumstantially https://github.com/johnmn3/cljs-thread/blob/master/src/cljs_thread/sync.cljs#L76
So that code on the main thread would still work with similar formalisms being used on worker threads, I made promises handle derefable
I might have had to do
(extend-type js/Promise
ICloneable
(-clone [p] (.then p)))to get it to work
hmm, so with your impl, if we call @(fetch in a function that is not async, but is called inside an async function, that wouldn't work right?
Just wonder if like, the compiler can just know somehow whether we're in an async context or not, and do the right thing depending
It's definitely magic-y
Wait, how does your impl know which context it's in?
if we call @(fetch in a function that is not async, but is called inside an async function, that wouldn't work right?
this is correct, but js-await also doens't work in that case, you need to mark it as asyncthe compiler knows
I read @ to a special form and then the compiler either translates that to deref or js-await based on if we're inside an async function
here's the commit: https://github.com/squint-cljs/squint/commit/ea5bc932d5b4f24eea4a2d36c1fec68a73aa61e3
Ah it's a dynamic binding *async*
That's awesome you can do it at compile time
Wait...
How does it know to do a normal deref, in the event the callee is actually an atom that wants to be derefed?
because I made the atom type be a promise as well
see commit
so (js-await (atom 1)) would return 1
ah I get it
Yeah I don't see the downsides. feels to me like it was meant to be
the only downside might be performance or so
but you can always write a manual (deref ..) is that is a concern
Is it possible to make an anonymous function ^:async? I was just trying to test nested async fns
yes:
^:async (fn [])(^:async fn []) also works
that's probably the preferred syntax since in JVM Clojure we have: (^:once fn* [] x)
Should this work?
(defn ^:async gettext []
(let [res ^:async (fn [] @(js/fetch ""))
json2 (-> @(.json (res)) (dissoc :headers))]
(set! js/app.innerHTML #html [:pre (js/JSON.stringify json)]))) can you share a playground link rather than pasting code?
by hitting the share button
and pasting it here
Well, that one has a typo json2
but I think it's throwing before it gets there
doing an await as the last step in an async function won't help you, you need to move the deref to the call site
ah okay
(defn ^:async gettext []
(let [res ^:async (fn [] (.json @(js/fetch "")))
json2 (-> @(res) (dissoc :headers))]
(set! js/app.innerHTML #html [:pre (js/JSON.stringify json2)])))
So the async stuff is working nestedlyI'd be curious what the performance trade off would be vs vanilla squint
vanilla squint? none I think
the generated code is the same as when you would've written js-await
i have to say i like the idea but i have a kind of feeling that that might cause some sideeffects i'm not aware of. But it could also be that i'm just wrong. So in general this is a great move to have it that way.
Well, one other downside: if people ever ended up using formalisms in squint that act like cljs-thread (allowing the user to use real blocking semantics) then there might be some semantic divergence there from Clojure. In Clojure, a deref usually implies a blocking semantic, where that wouldn't be true here. And if, in the future, you wanted to accommodate those more clojury semantics in squint, it might be complicated having deref do all the right things for all three scenarios (regular deref, promise resolution and legit blocking)
It's only a matter of time before https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait becomes more mainstream, with objects/data that can live in a SAB. So a future where you might want to let deref handle wait may well happen
btw, I'm thinking that storing squint's compiler/environment state in a SAB could make multi-threaded squint even more clojury than cljs-thread. You wouldn't have to capture all bindings in user-land and ship them "over the wire" to the worker - context would follow execution from thread to thread transparently
In cljs-thread, I'm using a service-worker hack to enable blocking semantics, but .wait is the right way going forward. I'd imagine the security constraints around COOP/COEP for SABs will smooth out in the future
I've got an almost working impl of squint-thread (using Atomics.wait) - maybe your newer repl-bits will make that easier