clojurescript

Raymond Ko 2025-12-17T01:29:02.481059Z

does anyone know if ClojureScript will eventually get a way to generate JS async and await? I've looked into this years ago and ended up using macros and Promises like promesa. But if I do this, stack traces don't work on node.js + serverless. They do work in the browser. This makes debugging async code very challenging in CLJS.

borkdude 2025-12-17T07:26:16.001529Z

FWIW squint and cherry have it right now. Don't know if CLJS will.

thheller 2025-12-17T07:28:08.668889Z

there is https://clojureverse.org/t/promise-handling-in-cljs-using-js-await/8998 as well if using shadow-cljs

๐Ÿ‘ 1
borkdude 2025-12-17T07:28:42.754349Z

Is that actually generating async await?

borkdude 2025-12-17T07:29:08.050279Z

I thought that was the OP question

thheller 2025-12-17T07:30:59.008309Z

no, it doesn't. but it does use native promises, so doesn't have the stacktrace issue (at least the last time I checked). it is also very close to the metal, so you won't see anything of the "library" on the stacktrace either.

borkdude 2025-12-17T07:32:39.721779Z

Ah native promises. Eventually other libs like promesa do use native promises too right? Agree that it does more than it should in CLjS. Contemplating a promesa-lite version ;-)

thheller 2025-12-17T07:34:04.992059Z

no clue, never used promesa myself, but there is a lot of "wrapper" code going on.

thheller 2025-12-18T08:08:05.086529Z

js-template basically only acts like str, it doesn't do any hiccup conversion and imho shouldn't. there could be another macro that does that conversion though, given that its a common use case.

borkdude 2025-12-18T08:11:07.258119Z

I don't mean: does it do hiccup, but: is it compose-able with something that uses hiccup (I don't think it is but it would be great if it was possible somehow)

borkdude 2025-12-18T08:14:14.920039Z

Another thought: now that ES6 is the baseline for CLjS we could simply let str emit a template literal and a tag on str changes the template function

borkdude 2025-12-18T08:14:46.486589Z

In squint str already does so (except for the tag)

thheller 2025-12-18T08:17:23.743399Z

no clue what you mean by composable with something that uses hiccup.

thheller 2025-12-18T08:17:54.594699Z

did you do some benchmarks to test whether js templates are actually faster than the concatenation done by str?

borkdude 2025-12-18T08:18:46.476659Z

They are equally fast. And since they are equally fast there's no reason we could just use plain str + tag for template literals

borkdude 2025-12-18T08:20:04.337579Z

I'll postpone the hiccup discussion to not confuse the discussion but the main issue is that writing html in strings for lit gets very tedious compared to hiccup

thheller 2025-12-18T08:21:03.754709Z

dunno how you'd add tags to str given that (str not-at-tag "foo") is probably common (`js-template` has the same problem, but by definition uses the first symbol as tag)

borkdude 2025-12-18T08:21:18.550149Z

As a tag on the str form

thheller 2025-12-18T08:22:01.387239Z

you mean override what ^lit/html (str "foo" bar) does? unsure what you mean by tag? many different ways of tagging in CLJS ๐Ÿ˜›

borkdude 2025-12-18T08:22:53.839189Z

Syntax wise yes, but what do you mean with override? Currently the tag here is a no-op?

thheller 2025-12-18T08:23:35.777489Z

not a no-op, it is added as the :ret-tag (or whatever that was exactly). rarely used, but used.

borkdude 2025-12-18T08:24:29.404859Z

True. If we expand str in a js* expr with string tag, would it fix that?

thheller 2025-12-18T08:25:08.717549Z

sorry, I need to see code example to follow. unsure how js* factors into this

borkdude 2025-12-18T08:25:32.228399Z

Look at the current expansion of str, it will become clear

borkdude 2025-12-18T08:26:58.811789Z

(Not at kbd right now)

borkdude 2025-12-18T11:34:06.721469Z

I have this working, in case you're interested.

cljs.user=> (defn my-string-template [xs & ys] (prn xs ys) "hello")
#'cljs.user/my-string-template
cljs.user=> (let [x 1] ^my-string-template (str2 1 2 3 x))
#js ["123" ""] (1)
"hello"

borkdude 2025-12-18T11:35:19.959899Z

and string type hint still works because of macroexpansion with the right tag:

cljs.user=> (let [x 1 y ^my-string-template (str2 1 2 3 x)] (inc y))
WARNING: cljs.core/+, all arguments must be numbers, got [string number] instead at line 1 
#js ["123" ""] (1)
"hello1"

borkdude 2025-12-18T11:35:52.906749Z

No idea why number is included in the warning, but this is the same warning as with:

cljs.user=> (let [x "foo"] (inc x))
WARNING: cljs.core/+, all arguments must be numbers, got [string number] instead at line 1 <cljs repl>
"foo1"

borkdude 2025-12-18T11:58:56.180309Z

switching over str2 to str: almost all tests passing (some compiler tests are failing, but will look into it once we agree that this is a promising direction)

shaunlebron 2025-12-18T20:51:18.051109Z

I havenโ€™t been paying too much attention to squint and shadowโ€™s implementations, but I wrote this proposal in 2018 that might still be relevant.

borkdude 2025-12-18T20:57:04.800199Z

@shaunlebron squint pretty much does what your proposal suggests but without the js- prefix in the keywords of the metadata and separate keywords for async + gen. It does use js-await and js-yield syntax. Try it on the squint playground

shaunlebron 2025-12-18T21:00:34.721719Z

perfect, yeah I like that better

Chris McCormick 2025-12-17T08:05:23.262639Z

async was the top missing language feature in the "state of cljs" 2024 survey.

thheller 2025-12-17T08:24:46.885399Z

all those features have been available in shadow-cljs for 4+ years ๐Ÿ˜› https://clojureverse.org/t/modern-js-with-cljs-class-and-template-literals/7450

thheller 2025-12-17T08:25:08.439719Z

well minus static typing of course

borkdude 2025-12-17T08:45:55.638159Z

in squint/cherry too :) (minus static typing)

schadocalex 2025-12-17T10:19:34.966499Z

but shadow-cljs js-async is nothing special compared to others libraries? It does not provide the syntax one would except. I use (a fork of) kitchen-async that allows to use it in many clojure forms + uses native Promise objects. I moved out from promesa but can't remember why. squint/cherry in comparison is the true async/await feature we are expecting (I guess)

thheller 2025-12-17T10:30:56.639409Z

I actually prefer the js-await syntax, since its immediately obvious where the async block action starts just by looking at the first word. digging for some nested await somewhere is less clear for me.

(js-await [x (some-code)]
  (occurs-when-async-action-completes x))

(let [x (await (some-code))]
  (occurs-when-async-action-completes x))

thheller 2025-12-17T10:31:31.451639Z

but I wrote it, so no surprise there ๐Ÿ˜›

borkdude 2025-12-17T10:32:29.356939Z

in squint it's js-await btw to align with the other js-reserved keywords, js-in, etc.

schadocalex 2025-12-17T10:34:25.224529Z

it just break the flow so that's why I don't like it. You can't use it inside a let, threading macros, etc. That's why (for me) promesa/kitchen-async is already better than js-await, but still not ideal as you don't control what is awaited or not (everything is wrapped by default)

thheller 2025-12-17T10:37:41.802729Z

I exactly do not want it to work in arbitrary positions since it just makes it very hard to know where an async split occurs, but yeah I get that it is different.

schadocalex 2025-12-17T10:41:02.568299Z

That's the point of the native JS async/await, you don't want to think much about where it splits. Else you can still use the then-fn/body syntax as your js-await (or any equivalent). But then I would not call shadow-cljs js-await the async/await feature. It's more a "js-then" (imho)

๐Ÿคท 1
schadocalex 2025-12-17T10:48:06.987609Z

(just to be clear, disagreeing on this doesnโ€™t mean anything, I love shadow-cljs, use it all the time, and Iโ€™m grateful to you for it. Amazing work)

borkdude 2025-12-17T10:48:26.155259Z

we all love shadow-cljs, no argument there :)

๐Ÿ™‚ 1
thheller 2025-12-17T10:52:53.888079Z

I got no issue with disagreements, I think thats useful. ๐Ÿ™‚ I think mostly the difference in design is due to either implementing it as a language feature with compiler support, or just a macro that can work anywhere with no adjustments to the compiler whatsoever. js-await is just that, a true async/await is not. I'm not saying that there shouldn't be compiler support, just that js-await is a trivial hack that has covered 100% of my uses to this date ๐Ÿ˜‰

schadocalex 2025-12-17T11:01:07.988499Z

I agree except I wouldn't call js-await a hack, it's just a macro. I also use the (-> thenable (.then (fn [x] ...)) syntax sometimes.

Harold 2025-12-17T13:56:47.535579Z

We also use [shadow.cljs.modern :refer (js-await)] and prefer it to both promisea and writing -> ... .then I have the same question as OP though, there are cases where I'd like to write cljs code that compiles to real js async functions and await calls to them. An important one that will impact us eventually is AWS node lambda discontinuing support for callback-based handlers in their Node 24 runtime: โ€ข https://dev.classmethod.jp/en/articles/aws-lambda-node-js-24/#3.%2520Verifying%2520Behavior%2520of%2520Deprecated%2520Callback%2520Format This is going to necessitate us eventually rewriting our node lambda handlers (we make use of the callback-style) and doing so with async/await in a way that avoids code nesting the way js programmers get to would be nice.

borkdude 2025-12-17T13:59:20.678509Z

why would you need to use async/await generated code instead of just dealing with raw promises via macros? AWS shouldn't care about that since you can do that in JS too

borkdude 2025-12-17T14:00:04.267789Z

anyway, if you really need to, squint or cherry could be a good tool to get async/await today

thheller 2025-12-17T14:01:51.513219Z

async functions are just regular functions that always return a promise. so if you make sure that your regular function always returns a promise then you have an async function ๐Ÿ˜‰

borkdude 2025-12-17T14:04:11.774479Z

some JS tools check for the AsyncFunction contructor I believe

> x = async function() {}
[AsyncFunction: x]
> x.constructor
[Function: AsyncFunction]
>
(for reasons unbeknownst to me but I know some do)

thheller 2025-12-17T14:27:31.762199Z

really? never seen that, but no doubt it exists ๐Ÿ˜›

Harold 2025-12-17T16:41:25.092509Z

> async functions are just regular functions that always return a promise. so if you make sure that your regular function always returns a promise then you have an async function This is only half of it. async functions also use generators under the hood to respect await calls inside them. Writing code in this way can really simplify certain things.

Harold 2025-12-17T16:42:12.016359Z

> anyway, if you really need to, squint or cherry could be a good tool to get async/await today I agree with this for some programs - however, the programs I am working on need real cljs

Harold 2025-12-17T16:45:40.362959Z

> why would you need to use async/await generated code instead of just dealing with raw promises via macros? AWS shouldn't care about that since you can do that in JS too also agreed that it's possible to express w/ raw promises, the code is just simpler if it can be written straight-line with async/await

borkdude 2025-12-17T16:52:38.373169Z

> need real cljs can you explain what you mean with real?

borkdude 2025-12-17T16:53:35.792789Z

I mean, I understand squint and cherry aren't the real CLJS, just curious what feature you mean specifically

Harold 2025-12-17T16:53:40.471739Z

We need npm deps, and GCL + GCC w/ advanced optimizations.

๐Ÿ‘ 1
Harold 2025-12-17T16:55:29.159079Z

Honestly, I tried the first version of these systems in squint a couple years ago, it would have been neat if we could have gone that route, but that ship has for now sailed (:

Harold 2025-12-17T16:56:11.118429Z

These programs are unusually sensitive to js artifact sizes, and squint can win that every time compared with cljs

borkdude 2025-12-17T17:01:10.710199Z

"These programs are unusually sensitive to js artifact sizes," can you explain more?

Harold 2025-12-17T17:02:38.407129Z

The smaller the zipped js is, the faster my program starts, and the more money I make ๐Ÿ˜ฎ

borkdude 2025-12-17T17:04:45.946309Z

make sense :)

1
dnolen 2025-12-17T17:59:43.082069Z

we are in fact pondering async/await and generators - I do like these features of squint. template strings has come up and I think that could be an easy one, #js "foo" seems fine to me for this

1
dnolen 2025-12-17T18:00:20.217309Z

ideally we could get this stuff into a release in 2026 sooner rather than later. Any help to get there appreciated of course.

Harold 2025-12-17T18:23:36.827559Z

We can stay on Node 22 into 2027 (https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html), so this is not urgent for us I think generators + async/await in cljs would be super-fun. I don't know if I'm smart enough to come up with the best possible cljs constructs - I do know that it shouldn't involve indentation ๐Ÿ™‚

Harold 2025-12-17T18:24:42.476459Z

#js "foo ${(* 6 7)}" โ‰๏ธ

borkdude 2025-12-17T18:30:43.240389Z

note that template strings also sometimes need a custom tag function, e.g.

lit`<html></html>
` how would you express this with #js "foo" and how would you express interpolation? I can help with async/await support in CLJS if you want @dnolen

dnolen 2025-12-17T19:02:54.741869Z

awesome! link for custom tag functions for me to read?

dnolen 2025-12-17T19:08:02.798049Z

nvm found it

dnolen 2025-12-17T19:16:46.099399Z

hrm ^lit #js "<html></html>" ... yeah needs thought. Thoughts welcome. But perhaps better in #cljs-dev

thheller 2025-12-17T19:22:28.423469Z

I think (js-template lit/html "

Hello " name "

") giving lit.html`<h1>Hello ${name}</h1>` is reasonable ๐Ÿคท

โž• 1
dnolen 2025-12-17T19:38:58.836899Z

I agree that's better than what I'm suggesting ๐Ÿ™‚

borkdude 2025-12-17T20:58:16.154649Z

in squint I have #html baked in, so you can write:

#html [:div "hello"]
and you can change the template function with
^lit #html [:div "hello"]
so yeah, I had that tag fn idea too ;) But this is not a generic way of writing template strings. I did like the idea of being able to generate html via hiccup and then feed that to lit/html. Is this possible with js-template @thheller?

Raymond Ko 2025-12-19T18:58:31.416699Z

Forgive me if the following is wrong, but it has been several years. But native async has lower overhead in V8: https://v8.dev/blog/fast-async

Raymond Ko 2025-12-19T19:00:29.551159Z

and it's tighter integration is what allows it to self capture traces during throw exceptions via (.-stack err) and .toString like if my Lambda crashes and it needs to log it.

Raymond Ko 2025-12-19T19:01:38.123539Z

My current solution I have used for years is have a custom mini version of Promesa, use Bluebird promises, override js/Promise and setup long stack traces via #?(:cljs (.config bluebird #js {:longStackTraces true}))

Raymond Ko 2025-12-19T19:02:20.099059Z

but obviously this is not performant, although it doesn't really matter for most CRUD style API request handling.

Raymond Ko 2025-12-19T19:03:12.212869Z

when using the V8 debugger, I do get the full stack traces, it just can't generate itself, unless real async is used. I'm just checking, but maybe my case is too niche.

borkdude 2025-12-19T19:04:22.647389Z

@raymond.w.ko you can use squint or cherry today to do native async. not sure how big your lambda is and if it requires rewriting stuff. I'm volunteering to backport it to CLJS as well if David is ok with it. Not sure about the timeframe but I'm sure it's doable (given enough time).

Raymond Ko 2025-12-19T19:07:03.475309Z

thanks for the suggestion. i need to look into it. it's pretty huge like several MBs as it has a lot of JS libraries in it. i personally call them "fat" lambdas and it's not really in the spirit of how serverless lambdas are suppose to be.