avatar image - a green paisley swirl bits all the way down
December 18, 2023
By: Nundrum

Stumbling Through Interop

》Surely I can just cargo cult this

After making so much process building a progressive web app, I decided to take a detour into making some "simpler" pages, without re-frame. To pull in some EDN data to work with, I quickly needed to figure out how to use fetch. JavaScript interop looks like Java interop, right? Objects, members, methods. What works in one place should work in the other. Right?

》Enter promises and consternation

I started playing around just by seeing what fetch returns:

(js/fetch edn-url ) ; => #object[Promise [object Promise]]

Getting a promise object in return was not a surprise. Previous work taught me that JS uses async code and promises heavily. Which makes sense — any blocking of a page’s JS thread leads to a frozen page. Treating the promise like a magic bottlecap, I reach for the handy old tools realized? and deref:

(realized? (js/fetch edn-url )); => :repl/exception!
(deref (js/fetch edn-url )); => :repl/exception!

Oh, no! This isn’t the same at all.

》Uncultured exploration

Once the naive approach failed, I tried to find CLJS examples that made sense to me. The best was at the end of this post.

;from https://nextjournal.com/fndriven/cljsjsinterop
(defn get-products-from-api []
  (-> (js/fetch api-endpoint)
      (.then #(.json %))
      (.then #(js->clj % :keywordize-keys true))
      (.then #(handler %))
    (.catch #(error-handler %))))

But example also led to a new problem: I was fetching EDN data, not JSON. I’m sure there’s no .edn method in JS. My next stop was to read about the fetch API.

A little digging and I started to understand that fetch is returning a Response inside a promise. And I can see there is a .text method on that Response that returns the response body text, which I presume can be used with read-string to turn into a Clojure data structure.

Here was the next attempt:

(-> (js/fetch edn-url )
    (.then #(.text %))
    (.then #(println (str %)))
    (.catch #(println (str "error: " %)))) ; => object[Promise [object Promise]]

Bah it’s a nested set of promises again. But my data printed out in the console! It’s not EDN yet, but Success! Sorta. I’m missing something. Why did this return the promises to the REPL? What’s happening with all these .then methods? The example doesn’t explain it.

Similar JS examples all use await but that is not implemented in CLJS. Though there are a number of libraries which might simplify this, now was the time to dig in so my understanding could be enhanced. Plus I wanted to attempt to avoid pulling in extra dependencies for a single fetch.

Next, I turned to the MDN guide to promises. They describe something similar to the mechanism in re-frame-http-fx where each fetch is described with a callback for successful fetches and another callback for failures.

But the fetch API does not expose this dual-callback interface! What’s going on?

》Cultivating promises

The deal is: fetch returns a promise containing the Response object.

The other deal is: there’s no (simple) way to return the result of an async function in the REPL. The content of the promise is only available in the callbacks.

How can that Response be used? We’re back to .then. The then method lets you supply the success/failure functions which will have access to the contents of the promise. Then also functions like await in JS, marking a place in execution that will be handled later, asynchronously.

This is why the REPL is barred from accessing the content — it is working in a different context than those supplied functions. Instead, you have to use the content in context. For example, fire off another async event or stuff the result in an atom:

(def state (atom nil))
(-> (js/fetch edn-url)
    (.then #(.text %))
    (.then #(cljs.reader/read-string %))
    (.then #(reset! state %))
    (.catch #(println (str "error: " %))))

The other tricky bit was understanding that .text is a method that returns another darn promise. Which means the body text can only be accessed in yet another .then.

OK, you ask, why the third .then in that example? I could have combined that in one step, such as the example below, but wanted to illustrate an important point that escaped me initially: .then always returns a promise. Once you’re in crazy mirror-universe .then-world, you have to stay there. Kind of like dealing with the Fae. So even the result of calling read-string ends up embedded in a promise.

;this works
(-> (js/fetch edn-url)
    (.then #(.text %))
    (.then #(reset! state (cljs.reader/read-string %)))
    (.catch #(println (str "error: " %))))

;this feels natural, but fails
(-> (js/fetch edn-url)
    (.then #(.text %))
    (.then #(cljs.reader/read-string %))
    (reset! state %)
    (.catch #(println (str "error: " %))))

"You’ve eaten the food and now must stay in Faerie" is the standard JS async/await paradigm. Only there’s no await in CLJS. It’s just as simple and natural for us to thread together those .then statements instead.

And that .catch? It’s the handler used if any of the async functions in the chain fail.

If the page had needed more than a single fetch, then cljs.core.async would have been a better fit.

》Occult reasons

"Why, Nundrum, why are you writing all of this rambling?"
— probably everyone who reads this blog

I suppose this is a bit of a rubber-ducking excercise. But another bit of it is to shine some light on where I see gaps in ClojureScript documentation.

The hardest part of getting started with Clojure is …​ integrating it with your editor and learning to craft code with the repl. Really. Lein or neil can get you going quite quickly, not to mention babashka! Then you get a wonderful language that’s shockingly different from Java and you don’t have to know any Java.

Clojure knowledge can be built up nicely. You can get quite a long way with slurp before you have to deal with any filesystem or network interop. But my CLJS expereince is just not like that. I thought maybe it would help others to see my struggles, and maybe give The Programmers That Be an idea of the rough parts. If you have suggestions, please hit me up at the Mastodon address in the link bar. Or find me as @Nundrum on Clojurians.

Tags: async interop clojurescript
« Building A ClojureScript SVG Toolbox From Clojure to ClojureScript »

A blog by Nundrum

Links

  • @Nundrum on Mastodon
  • Archives
  • RSS
  • Clojurians Slack
  • Contacting Nundrum
  • P.E.P.R. Jacket Project

Recent Posts

  • Fountainvoid
  • Introducing XScreenBane
  • A Jacket, Microcontrollers, and Clojure

Tags

  • re-frame
  • clojure
  • svg
  • async
  • interop
  • jna
  • screensaver
  • platformio
  • font
  • pepr jacket project
  • cyberpunk
  • clojurescript

Copyright © 2025 Nundrum

Powered by Cryogen