I found it surprisingly tricky to define a macro to do error handling in both clj and cljs. I assumed it was a simple matter of swapping Exception with js/Error, but it turned out to be more complicated than that.
At first, I tried this:
(defmacro my-macro
  [& forms]
 `(try
    ~@forms
    (catch #?(:clj Exception :cljs js/Error) e#
      ,,,)))
But this produced Exception every time. I soon realized that the problem was that the macro was being called during the compilation of my cljs files, which happens in a clj environment. Therefore, I would have to have the macro return a form that would resolve to the correct exception class at runtime. I tried this:
(def exception-class
  #?(:clj Exception :cljs js/Error))
(defmacro my-macro
  [& forms]
 `(try
    ~@forms
    (catch exception-class e#
      ,,,)))
Now it worked in cljs, but not in clj!!! After some experimentation, I discovered that JVM Clojure (apparently) does not allow you to refer to the exception class indirectly. You have to refer to Exception directly by name.
So finally, I settled on this:
(def fake-java
  #?(:cljs (clj->js {:lang {:Exception js/Error}})))
(defmacro my-macro
  [& forms]
 `(let [~'java fake-java]
    (try
      ~@forms
      (catch Exception e#
        ,,,))))
Exception expands to java.lang.Exception, which now resolves to the correct exception class at runtime in both clj and cljs.
My question is, is there a better way to this? And why does JVM Clojure not allow referring to the exception class indirectly, but ClojureScript does?
Update
With ClojureMostly's help, I have refactored the macro like this, and it works:
(defmacro my-macro
  [& forms]
 `(try
    ~@forms
    (catch ~(if (:ns &env) 'js/Error 'Exception) e#
      ,,,)))
                You could refactor your macro to be expressed in terms of a function call.  This function would accept a "thunk" of the forms and wrap it in the try (rather than doing this directly in the macro).
For example:
(defmacro my-macro [& forms]
  `(my-macro* (fn []
                ~@forms)))
Then you can define my-macro* as:
(defn my-macro* [f]
  (try
    (f)
    (catch #?(:clj Exception :cljs js/Error) e
      ...)))
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With