cljc 宏中的错误处理
Error handling in cljc macro
我发现在 clj 和 cljs 中定义一个宏来进行错误处理非常棘手。我以为把 Exception
和 js/Error
交换是一件简单的事情,但事实证明比这更复杂。
起初,我试过这个:
(defmacro my-macro
[& forms]
`(try
~@forms
(catch #?(:clj Exception :cljs js/Error) e#
,,,)))
但这每次都会产生 Exception
。我很快意识到问题是在 cljs 文件编译期间调用了宏,这发生在 clj 环境中。因此,我必须让宏 return 成为一种可以在运行时解析为正确异常 class 的形式。我试过这个:
(def exception-class
#?(:clj Exception :cljs js/Error))
(defmacro my-macro
[& forms]
`(try
~@forms
(catch exception-class e#
,,,)))
现在它在 cljs 中有效,但在 clj 中无效!!!经过一些实验,我发现 JVM Clojure(显然)不允许您间接引用异常 class。您必须直接通过名称引用 Exception
。
最后,我决定这样做:
(def fake-java
#?(:cljs (clj->js {:lang {:Exception js/Error}})))
(defmacro my-macro
[& forms]
`(let [~'java fake-java]
(try
~@forms
(catch Exception e#
,,,))))
Exception
扩展为 java.lang.Exception
,现在在 clj 和 [=38= 中都在运行时解析为正确的异常 class ]cljs.
我的问题是,有更好的方法吗?为什么 JVM Clojure 不允许间接引用异常 class,但 ClojureScript 允许?
更新
在 ClojureMostly 的帮助下,我像这样重构了宏,并且它有效:
(defmacro my-macro
[& forms]
`(try
~@forms
(catch ~(if (:ns &env) 'js/Error 'Exception) e#
,,,)))
您可以重构您的宏以根据函数调用来表达。此函数将接受 "thunk" 形式并将其包装在 try
中(而不是直接在宏中执行此操作)。
例如:
(defmacro my-macro [& forms]
`(my-macro* (fn []
~@forms)))
那么你可以定义my-macro*
为:
(defn my-macro* [f]
(try
(f)
(catch #?(:clj Exception :cljs js/Error) e
...)))
通常的做法是 check for the :ns
key in the special &env
binding that you have in a defmacro
. Copied from plumatic/schema
:
(defn cljs-env?
"Take the &env from a macro, and tell whether we are expanding into cljs."
[env]
(boolean (:ns env)))
(defmacro try-catchall
"A cross-platform variant of try-catch that catches all exceptions.
Does not (yet) support finally, and does not need or want an exception class."
[& body]
(let [try-body (butlast body)
[catch sym & catch-body :as catch-form] (last body)]
(assert (= catch 'catch))
(assert (symbol? sym))
(if (cljs-env? &env)
`(try ~@try-body (~'catch js/Object ~sym ~@catch-body))
`(try ~@try-body (~'catch Throwable ~sym ~@catch-body)))))
用法:
(macros/try-catchall (f) (catch e# ::exception))
我发现在 clj 和 cljs 中定义一个宏来进行错误处理非常棘手。我以为把 Exception
和 js/Error
交换是一件简单的事情,但事实证明比这更复杂。
起初,我试过这个:
(defmacro my-macro
[& forms]
`(try
~@forms
(catch #?(:clj Exception :cljs js/Error) e#
,,,)))
但这每次都会产生 Exception
。我很快意识到问题是在 cljs 文件编译期间调用了宏,这发生在 clj 环境中。因此,我必须让宏 return 成为一种可以在运行时解析为正确异常 class 的形式。我试过这个:
(def exception-class
#?(:clj Exception :cljs js/Error))
(defmacro my-macro
[& forms]
`(try
~@forms
(catch exception-class e#
,,,)))
现在它在 cljs 中有效,但在 clj 中无效!!!经过一些实验,我发现 JVM Clojure(显然)不允许您间接引用异常 class。您必须直接通过名称引用 Exception
。
最后,我决定这样做:
(def fake-java
#?(:cljs (clj->js {:lang {:Exception js/Error}})))
(defmacro my-macro
[& forms]
`(let [~'java fake-java]
(try
~@forms
(catch Exception e#
,,,))))
Exception
扩展为 java.lang.Exception
,现在在 clj 和 [=38= 中都在运行时解析为正确的异常 class ]cljs.
我的问题是,有更好的方法吗?为什么 JVM Clojure 不允许间接引用异常 class,但 ClojureScript 允许?
更新
在 ClojureMostly 的帮助下,我像这样重构了宏,并且它有效:
(defmacro my-macro
[& forms]
`(try
~@forms
(catch ~(if (:ns &env) 'js/Error 'Exception) e#
,,,)))
您可以重构您的宏以根据函数调用来表达。此函数将接受 "thunk" 形式并将其包装在 try
中(而不是直接在宏中执行此操作)。
例如:
(defmacro my-macro [& forms]
`(my-macro* (fn []
~@forms)))
那么你可以定义my-macro*
为:
(defn my-macro* [f]
(try
(f)
(catch #?(:clj Exception :cljs js/Error) e
...)))
通常的做法是 check for the :ns
key in the special &env
binding that you have in a defmacro
. Copied from plumatic/schema
:
(defn cljs-env?
"Take the &env from a macro, and tell whether we are expanding into cljs."
[env]
(boolean (:ns env)))
(defmacro try-catchall
"A cross-platform variant of try-catch that catches all exceptions.
Does not (yet) support finally, and does not need or want an exception class."
[& body]
(let [try-body (butlast body)
[catch sym & catch-body :as catch-form] (last body)]
(assert (= catch 'catch))
(assert (symbol? sym))
(if (cljs-env? &env)
`(try ~@try-body (~'catch js/Object ~sym ~@catch-body))
`(try ~@try-body (~'catch Throwable ~sym ~@catch-body)))))
用法:
(macros/try-catchall (f) (catch e# ::exception))