Async/Await in ClojureScript eingeführt
(clojurescript.org)- ClojureScript 1.12.145 ändert den Compiler so, dass Funktionen mit dem Hinweis
^:asyncals JavaScript-async functionausgegeben werden - Damit lassen sich ClojureScript-Funktionen schreiben, die mit
awaitauf Promise-Werte warten, was die JavaScript-Interoperabilität verbessert - Auch in Tests kann
^:asyncverwendet werden, und mitawaitlassen sich Ergebnisse asynchroner Funktionsaufrufe prüfen - In der jüngsten Clojure-Umfrage machte die Unterstützung von async functions den größten Anteil unter den gewünschten ClojureScript-Verbesserungen rund um die JavaScript-Interoperabilität aus
- In typischen Fällen beim Umgang mit aktuellen Browser-APIs und beliebten Bibliotheken sinkt der Bedarf an zusätzlichen Abhängigkeiten; die vollständige Liste der Änderungen ist im Eintrag 1.12.145 des ClojureScript-Changelogs zu finden
Verwendung von ^:async und await
- ClojureScript 1.12.145 ändert den Compiler so, dass Funktionen mit dem Hinweis
^:asyncals JavaScript async function ausgegeben werden - Da ClojureScript nun auf ECMAScript 2016 abzielt, können Verbesserungen bei der JavaScript-Interoperabilität gezielter ausgewählt werden
- Es ist nun möglich, ClojureScript-Funktionen zu schreiben, die mit
awaitaufPromise-Werte warten(refer-global :only '[Promise]) (defn ^:async foo [n] (let [x (await (Promise/resolve 10)) y (let [y (await (Promise/resolve 20))] (inc y)) ;; not async f (fn [] 20)] (+ n x y (f)))) - Auch in Tests kann
^:asyncverwendet werden, und mitawaitlassen sich die Ergebnisse asynchroner Funktionsaufrufe prüfen(deftest ^:async defn-test (try (let [v (await (foo 10))] (is (= 61 v))) (let [v (await (apply foo [10]))] (is (= 61 v))) (catch :default _ (is false))))
Hintergrund und Liste der Änderungen
- In der jüngsten Clojure-Umfrage machte die Unterstützung von async functions den größten Anteil unter den gewünschten ClojureScript-Verbesserungen rund um die JavaScript-Interoperabilität aus
- Durch diese Verbesserung sinkt in typischen Fällen beim Umgang mit aktuellen Browser-APIs und beliebten Bibliotheken der Bedarf an zusätzlichen Abhängigkeiten
- Die vollständige Liste an Korrekturen, Änderungen und Verbesserungen ist im Eintrag 1.12.145 des ClojureScript-Changelogs zu finden
- An ClojureScript 1.12.145 hat das Community-Mitglied Michiel Borkent mitgewirkt
1 Kommentare
Hacker-News-Kommentare
borkdude hat diesen Thread gepostet, und ich habe gesehen, dass er auch als Mitwirkender an diesem Release aufgeführt ist.
Die Argumente gegen async/await-Unterstützung waren schon lange im Wesentlichen zwei: dass dafür tiefgreifende Änderungen im gesamten CLJS-Compiler nötig wären, und dass Makros aus Bibliotheken wie Promesa bereits einen ähnlichen Komfort bieten.
Daneben gab es auch Stimmen, die meinten, man könne einfach core.async verwenden oder dass eine ausdrucksorientierte Sprache nicht gut zu async/await passe, aber das waren eher individuelle Ansichten als die wiederholt vorgebrachten Hauptargumente im Forum.
borkdude hatte im Clojurians Slack einmal gesagt, dass er nicht überzeugt sei, dass das Hinzufügen von Support unrealistisch sei, und es wirkt so, als hätte er sich letztlich die Zeit genommen, es umzusetzen — dafür bin ich wirklich dankbar.
Interessant ist, dass ClojureScript das asynchrone Paradigma schon lange vor dem Einzug von async/await in JavaScript selbst mit der Bibliothek core.async unterstützt hat.
Das soll den Wert dieses Releases überhaupt nicht schmälern; ich meine nur, dass es ziemlich cool ist, per Hinzufügen genau einer Bibliothek als Abhängigkeit ein neues Sprachfeature nutzen zu können, das die Host-Sprache noch gar nicht hat. Clojure ist großartig.
Ich glaube, ich habe davon durch einen Vortrag von David Nolen erfahren.
Später bin ich dazu übergegangen, im Frontend möglichst wenig JavaScript zu verwenden, und SSE ist einseitig, genau das macht es so schön. Es freut mich, dass sich derzeit Entwickler aus vielen Sprach-Communities für SSE interessieren.
David Nolens jüngerer Vortrag „A ClojureScript Survival Kit“ war ebenfalls gut: https://youtu.be/BeE00vGC36E
Für die Arbeit, die David „Swannodette“ Nolen seit den Anfangstagen von ClojureScript und core.async geleistet hat, kann man gar nicht dankbar genug sein. Besonders erstaunlich an diesem Vortrag ist, dass er tatsächlich auch Vorfreude auf eine Richtung zeigt, in der man ClojureScript hinter sich lässt und stattdessen pures serverseitiges Clojure, Server-Sent Events und nur eine winzige Menge JavaScript verwendet.
Die eigentliche Demo beginnt ungefähr bei 26:30. Nachdem er den Ressourcenverbrauch einer im Client laufenden Web-App zeigt, demonstriert er dieselbe Web-App auf dem Server mit einseitigem Push per SSE zum Client, und der Ressourcenverbrauch sinkt fast auf null — ziemlich eindrucksvoll.
Das passt sicher nicht für alle Fälle, aber mit einer minimalen DOM-Manipulationsbibliothek wurde es deutlich leichter, über Web-App und Zustand nachzudenken. Früher musste ich sowohl eine Clojure-REPL als auch eine ClojureScript-REPL offen haben und viel bidirektionalen Traffic sowie schwer reproduzierbare Zustände handhaben; jetzt geht alles viel schneller und ist auch leichter reproduzierbar.
Das JavaScript-Output wird größer, es gibt kein eingebautes Fehlermodell, und wenn etwas schiefgeht, wird der Code in schwer lesbare und schwer zu debuggende Zustandsmaschinen umgewandelt.
Außerdem kann das
go-Makro keinen Code außerhalb seines eigenen S-Ausdrucks transformieren, was dazu verleitet, Funktionen unnötig groß werden zu lassen.Wie jemand von Cognitect einmal sagte: „core.async ist wunderschöner Unsinn“.
Es überrascht mich, dass Clojure/ClojureScript in letzter Zeit plötzlich häufiger in sozialen Medien auftaucht.
Ich habe es um 2012 herum einige Jahre beruflich genutzt, bin dann aber wie viele andere vom JVM weg zu getypten funktionalen Sprachen gewechselt.
Liegt das wachsende Interesse heute an agentischem Coding? Weil es keine Typprüfung gibt und weniger falsch geschriebene Syntaxfehler oder reservierte Wörter, sodass man Code schneller überfliegen kann? Erleben S-Ausdrücke gerade ein Comeback?
Die ernsthaften Clojure-Codebasen, die ich kenne, investieren stark in Testsuiten, sodass man mit ein paar Techniken, um einer KI den effektivsten Umgang mit der Testsuite beizubringen, ziemlich gut vorankommen kann.
Einige Kollegen lassen ihre Agenten auch mit der REPL interagieren, was schneller sei, weil nicht jedes Mal die Startkosten anfallen. Dafür war ich bisher zu faul, aber schon jetzt ist es schnell genug.
Clojure hat wenig Reibung. Alles außer
falseundnilist wahr, es gibt keine Operatorprioritätstabelle, und die Kernsprache unterstützt unveränderliche, persistente Datenstrukturen standardmäßig.Alles ist ein Ausdruck, keine Struktur, in der Operatoren und Ausdrücke vermischt sind.
map,reduce,filtersind eingebaut und werden im normalen Code selbstverständlich verwendet.Clojure-Code, den ich vor 10 Jahren geschrieben habe, läuft heute mit hoher Wahrscheinlichkeit noch, und Ökosystem sowie Sprachdesigner behandeln es fast wie ein Tabu, bestehenden Code zu brechen.
Von allen Sprachen, die ich verwendet habe, ließ sich darin am freiesten ausdrücken, bei gleichzeitig den wenigsten Kopfschmerzen. Flowstorm, praktisch ein Rückwärts-Debugger, ist außerdem ein traumhaftes Werkzeug fürs Programmieren.
Wenn man zufrieden arbeiten will, ist es eine wirklich gute Sprache. Umgekehrt nehmen die meisten Nutzer das einfach als selbstverständlich hin und reden deshalb nicht viel darüber.
Unter Programmierern, die Clojure kommerziell einsetzen, gibt es auch viele, die die Sprache nicht wirklich gut verstehen und deshalb nicht besonders glücklich damit sind. Oft haben sie sie nicht selbst gewählt oder waren noch nicht so weit, und ich denke, man muss vor Clojure ungefähr 10 Jahre lang die Dinge erlebt haben, die man in anderen Sprachen nervig fand.
Die Software-Videos von Clojure-Erfinder Rich Hickey sind zwar berühmt und einflussreich, aber das heißt nicht, dass Kollegen sie gesehen hätten oder sich dafür interessieren.
Eine große ungetypte Python-Codebasis mit KI zu bearbeiten war mühsam. Bei allem, was nicht durch Tests abgedeckt war, war es zu langweilig zu prüfen, ob wirklich nichts kaputtgegangen ist.
Je stärker das Typsystem, desto besser. Außerdem werden KI-Modelle auf Code trainiert, daher liegt nahe, dass sie mit populäreren Sprachen besser umgehen. ClojureScript ist gut, aber keine Mainstream-Sprache, daher würde ich erwarten, dass KI damit schlechter performt als mit JavaScript.
Wenn man also KI mitdenkt, ist es letztlich besser, eine getypte Sprache zu wählen — oder bei dynamischen Sprachen zumindest eine mit Type Hints.
Das ist wirklich groß. Seit der Ankündigung von Jank gab es im Clojure-Ökosystem nicht mehr so viel Vorfreude.
Ich wünschte, JavaScript-Alternativen im Frontend würden nicht nur obskur bleiben, sondern sich wirklich etablieren.
Ich würde gern etwas wie ClojureScript verwenden, aber außerhalb persönlicher Side Projects fällt es mir schwer, mir vorzustellen, wo man es einsetzen könnte. Wenn eine Organisation ohnehin schon Clojure im Backend nutzt, wäre die Einführung vielleicht leichter.
In Produktion habe ich es nicht eingesetzt, aber ich habe ein paar Side Projects und Dinge für die Familie damit ausgeliefert. Reagent, der React-Wrapper für ClojureScript, ergab für mich ehrlich gesagt mehr Sinn als React selbst.
Man baut HTML mit Hiccup, und Komponenten sind einfach nur Funktionen innerhalb eines Hiccup-DSL, das selbst im Grunde ebenfalls nur aus Listen besteht, wodurch das Ergebnis sehr sauber wird. Statisches sieht statisch aus, Dynamisches klar dynamisch, und es fühlte sich nach viel weniger Magie an als gewöhnliches React.
Unangenehm wurde es nur, wenn ich nicht-funktionale Komponenten aus NPM verwenden wollte. Nicht fatal, aber der Code wurde hässlich. Man konnte das mit Wrappern beheben, aber einige JS-Bibliotheken sind in cljs von Haus aus ziemlich chaotisch.
Die Community ist außerdem sehr freundlich und reif.
Schreib zuerst ein paar persönliche Skripte um, entwickle ein Gefühl dafür und spüre dann die Vorteile. Es ist nicht in jedem Fall besser, aber wenn später Leute dich um Rat fragen, solltest du selbst überzeugt genug sein.
Wenn man eine fremde Technologie einführt, ist es gut, etwas weniger Wichtiges auszuwählen, es neu zu schreiben und dann einfach laufen zu lassen. Falls es Probleme gibt, kann man leicht zurückrudern; wenn die Leute anfangen, es zu mögen, kann man es schrittweise ausweiten.
Als ich früher F# heimlich in eine .NET-Organisation eingeführt habe, habe ich auch zuerst weniger wichtige Tests in F# geschrieben.
https://blisswriter.app/
https://blog.nestful.app/p/how-we-dropped-vue-for-gleam-and
Ich habe cljs schon länger nicht mehr verfolgt, aber ursprünglich wurde es meiner Erinnerung nach ungefähr als „Clojure auf JavaScript“ beschrieben. Zumindest, soweit ich mich erinnere, hat Rich es anfangs so erklärt.
Ich verstand die Absicht so, dass es möglichst nah an einer weiteren Runtime bleiben sollte.
Diese Änderung hier wirkt aber so, als würde sie ein Feature hinzufügen, das es nur in cljs gibt, und
awaitkollidiert zudem mit dem bestehenden Keyword inclojure.core.Ich frage mich, ob die beiden Implementierungen sich mit der Zeit auseinanderentwickelt haben oder ob dieses Feature den Nutzern so wichtig war, dass ihnen der Unterschied das wert war.
Es ist wichtig, weil man JavaScript-Interop ohne zusätzliche Bibliothek handhaben kann.
Dieses Feature hat lange gefehlt, daher ist dieses Release ziemlich willkommen.
Es scheint mir die bessere Verarbeitungsweise zu sein, async/await-Funktionen in CSP einzupacken. Clojure hatte dafür bereits ein besseres Muster.
core.async verschwindet dadurch nicht, und wenn async/await besser passt als eine Promise-basierte Umsetzung, könnte auch der
.cljs-Teil von core.async aktualisiert werden.Ich glaube nicht, dass diese neue Funktionsmarkierung diesen Ansatz verdrängen wird.
https://clojurescript.org/guides/promise-interop#using-promi...
Ich weiß nicht recht, wie ich das einordnen soll. Einer der Kerngedanken von core.async war doch, all diese Dinge in Channels zu schieben, oder?
Ich bin nicht sicher, ob ein JavaScript-artiges
async-Keyword wirklich ein Upgrade ist.Man muss es nicht verwenden, und man kann weiterhin core.async nutzen. In der letzten ClojureScript-Umfrage war das außerdem das am häufigsten gewünschte Feature.