Hi, everyone! I'm building a ClojureScript backend with async API calls. I have a handler that calls an external API and redirects the user. All errors result in the same user-facing error page. Which approach is considered best practice? Option A: Return error maps
;; api.cljs
(defn create-thing [ctx id]
(async
(let [response (await (fetch url opts))]
(if (oget response :ok)
(let [data (await (.json response))]
{:id (:id data) :token (:token data)})
{:error :api-error
:status (oget response :status)}))))
;; routes.cljs
(defn handler [ctx req res]
(async
(try
(let [id (oget req [:query :id])
result (await (create-thing ctx id))]
(if (:error result)
(redirect-to-error res)
(.redirect res (build-url result))))
(catch js/Error e
(report-error! e)
(redirect-to-error res)))))
Option B: Throw exceptions
;; api.cljs
(defn create-thing [ctx id]
(async
(let [response (await (fetch url opts))]
(when-not (oget response :ok)
(throw (ex-info "API error" {:status (oget response :status)})))
(let [data (await (.json response))]
{:id (:id data) :token (:token data)}))))
;; routes.cljs
(defn handler [ctx req res]
(async
(try
(let [id (oget req [:query :id])
result (await (create-thing ctx id))]
(.redirect res (build-url result)))
(catch js/Error e
(report-error! e)
(redirect-to-error res)))))
As a python dev I feel that option B is cleaner since all errors are handled the same way. But from what I understand clojure way is to return data. What's actually the idiomatic approach here?Exceptions are perfectly fine in Clojure. It's just that sometimes they get in the way of other things:
• Doing many things and proceeding regardless of failures - you have to wrap every step in try/`catch` and convert exceptions into maps
• You can't recur from try or catch because they are not tail positions - there can be a finally clause
• If you already have functions that return "error or result" data maps, it might be easier to just stick to those structures instead of going back and forth between data and exceptions
When those aren't a concern, I myself stick to exceptions for, well, exceptional situations. Abnormal flows, basically. Definitely don't use exceptions for regular flow control, like a goto.
We had the same question a few days ago: https://clojurians.slack.com/archives/C053AK3F9/p1765727779342649
know how you can name an anonymous function (`(fn a-name [] nil)`)? Can something similar be done with reify?
Reify methods all get passed the reified object as the first parameter
I was thinking, if you (def f (fn a-name)) and eval f you get “#function[user/a-name--61550]”
but (def r (reify)) -> “#object[user$reify__61554 0x552552d0 “user$reify__61554@552552d0"]” and no way to get a name in there.
What are you trying to do with the name? You can always override .toString or create a new protocol/interface to override:
> (str (reify
Object
(toString [_]
"what up yo")))
"what up yo"nothing fancy, in this case I have a donut system and i was checking that a component got started properly so I eval’d it; the start function tries to provide one implementation and falls back to another and if they were both made with reify you can’t tell which one you’ve got just by looking