Making SSE Token Streams Resumable and Cancellable
Technical strategies for building robust and flexible server-sent event token streams.
![[ClojureScript]: Enhancing Asynchronous Development with Async/Await](https://res.cloudinary.com/dobyanswe/image/upload/w_1200,f_auto,q_auto/v1778228668/blog/2026/clojurescript-adds-async-await-support-2026.jpg)
For years, ClojureScript developers have navigated the intricate world of asynchronous operations with a toolkit that, while powerful, often felt like a creative workaround. We’ve embraced cljs.core.async with its elegant CSP model, wrestled with promise chaining, and utilized various third-party libraries to bring a semblance of sequential clarity to operations that inherently unfold over time. The consensus, whispered in forums and debated in community channels, was clear: JavaScript’s native async/await syntax offered a level of readability and ergonomic ease that was, frankly, aspirational. Today, with the release of ClojureScript 1.12.145, that aspiration becomes a grounded reality. This is not merely an incremental update; it’s a pragmatic embrace of modern JavaScript features that fundamentally streamlines asynchronous development for ClojureScript practitioners.
The journey to native async/await support in ClojureScript has been a testament to the language’s evolution. By officially targeting ECMAScript 2016, ClojureScript now possesses the capability to directly emit JavaScript async functions. This move is significant because it bypasses the need for intricate transpilation layers or external dependencies for common browser API interactions that rely heavily on Promises. The result is a cleaner, more performant, and undeniably more developer-friendly way to handle operations that don’t complete immediately. This isn’t about replacing the core tenets of ClojureScript or its functional paradigm; it’s about enhancing its interoperability with the JavaScript ecosystem, bringing a more idiomatic and intuitive syntax to a crucial aspect of modern web development.
^:async Declaration: A Promise of SimplicityThe most direct manifestation of this new capability lies in the ^:async metadata hint applied to function definitions. This simple declaration tells the ClojureScript compiler to treat a function as a JavaScript async function, unlocking the power of await within its scope. Consider the elegance this introduces for operations that previously required a more verbose approach.
Imagine fetching data from an API. In the pre-async/await era, you might have chained .then() calls, or perhaps orchestrated this within a core.async channel. While functional and effective, the sequential flow could become convoluted, especially with multiple dependent asynchronous steps. Now, with ^:async, the code transforms into something that feels remarkably familiar to anyone who has worked with modern JavaScript.
(defn ^:async fetch-user-data [user-id]
(let [user-response (await (js/fetch (str "/api/users/" user-id)))
user-json (await (.json user-response))
posts-response (await (js/fetch (str "/api/users/" user-id "/posts")))
posts-json (await (.json posts-response))]
{:user user-json :posts posts-json}))
This code snippet speaks volumes. The await keyword, used directly on the results of js/fetch (which returns a JavaScript Promise), pauses the execution of fetch-user-data until that Promise resolves. The subsequent .json() call, also asynchronous, is awaited in turn. The entire sequence reads like a synchronous piece of code, gracefully handling the underlying asynchronous nature of network requests without explicit callbacks or complex promise constructors. This readability is a game-changer, reducing cognitive load and making complex asynchronous workflows significantly easier to reason about and debug.
Furthermore, this extends to testing. deftest now supports the ^:async metadata, allowing you to write asynchronous tests in a similarly streamlined fashion.
(deftest ^:async test-fetch-user-data
(let [mock-user {:id 1 :name "Alice"}
mock-posts [{:id 101 :title "First Post"}]]
;; Mocking fetch to return resolved promises
(with-redefs [js/fetch (fn [url]
(if (re-find #"users/" url)
(if (re-find #"/posts" url)
(Promise/resolve #js {:json (fn [] (Promise/resolve mock-posts))})
(Promise/resolve #js {:json (fn [] (Promise/resolve mock-user))}))
(Promise/reject #js (.-Error "Not Found"))))]
(let [result (await (fetch-user-data 1))]
(is (= (:user result) mock-user))
(is (= (:posts result) mock-posts))))))
The ability to await within tests that themselves are marked as ^:async means you can directly test asynchronous functions without resorting to done callbacks or manually managing test timeouts for asynchronous operations. This is a significant win for developer productivity and test suite maintainability.
While the syntax is the most visible change, the deeper impact lies in the pragmatic considerations for ClojureScript developers. Historically, the community has debated the merits of cljs.core.async versus promise-based asynchronous patterns. core.async brought a powerful, CSP-inspired concurrency model to ClojureScript, which excels at managing complex communication between concurrent processes. However, for many common asynchronous tasks, particularly those involving direct interaction with browser APIs or external JavaScript libraries that are Promise-based, core.async could feel like overkill, or at least less direct.
The introduction of native async/await fills this gap with remarkable efficacy. It provides a more natural, idiomatic way to interact with the vast JavaScript ecosystem. For developers who have found themselves bridging the gap between ClojureScript’s functional purity and JavaScript’s promise-driven world, this new support offers a welcome reduction in cognitive friction. The need for third-party libraries like promesa or kitchen-async, while valuable in their time for providing async/await-like abstractions, is diminished for many common use cases. This move towards native support means fewer dependencies and a more direct mapping to the underlying JavaScript runtime.
However, it’s crucial to acknowledge the limitations and understand where this new feature shines and where existing patterns might still be preferable. The await keyword is strictly confined to functions declared with ^:async, mirroring JavaScript’s own constraints. This means you cannot await outside of an async context, which is a sensible restriction to prevent unexpected behavior.
Furthermore, the relationship between core.async and the new async/await is not one of replacement, but of coexistence and potential synergy. core.async’s go blocks can now leverage async/await under the hood for their own asynchronous operations. This could lead to performance improvements and better stack traces when debugging go blocks that interact with Promises. However, the fundamental CSP model of core.async—with its channels and explicit message passing—remains distinct from the promise-based, sequential flow of async/await. If your application’s concurrency model is deeply rooted in the channel-based paradigm for complex inter-process communication, core.async will continue to be your primary tool, now potentially augmented by async/await for specific Promise interactions.
There are also environments where native async/await might not be as effectively transpiled, or where older JavaScript runtimes are a concern. In such niche cases, developers might need to ensure their build tools are configured to handle the generated code appropriately.
The addition of native async/await support in ClojureScript 1.12.145 is more than just a feature; it’s a significant quality-of-life improvement. It represents a mature and pragmatic step towards aligning ClojureScript with the contemporary patterns of the JavaScript ecosystem. For developers writing code that frequently interacts with browser APIs, third-party JavaScript libraries, or any asynchronous operation based on Promises, this change brings a welcome dose of clarity, readability, and developer velocity.
This is not about abandoning the functional elegance of ClojureScript. Instead, it’s about providing a more direct and intuitive path for handling one of the most pervasive challenges in modern development: asynchronous operations. By enabling developers to write sequential-looking code that gracefully handles asynchronous events, ClojureScript is making itself an even more compelling choice for building robust, modern web applications. The era of wrestling with complex promise chains or feeling like you’re fighting the language to handle asynchronous tasks is, for many common scenarios, coming to a more elegant conclusion. This is a win for productivity, a win for readability, and a significant step forward for the ClojureScript developer experience.