在cljc宏中的错误处理

6
我发现在clj和cljs中定义一个宏来处理错误是非常棘手的。我以为只需要用js/Error替换Exception就可以了,但实际上比这更加复杂。
起初,我尝试了以下代码:
(defmacro my-macro
  [& forms]
 `(try
    ~@forms
    (catch #?(:clj Exception :cljs js/Error) e#
      ,,,)))

但是每次这样做都会产生一个异常。我很快意识到问题在于宏被调用时正在编译我的cljs文件,这发生在clj环境中。因此,我需要让宏返回一个表单,在运行时解析为正确的异常类。我尝试了这个:

(def exception-class
  #?(:clj Exception :cljs js/Error))

(defmacro my-macro
  [& forms]
 `(try
    ~@forms
    (catch exception-class e#
      ,,,)))

现在它在cljs中可以工作,但在clj中却不行!!! 经过一些试验,我发现JVM Clojure(显然)不允许您间接引用异常类。您必须直接通过名称引用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,现在在cljcljs中都能正确解析运行时的异常类。
我的问题是,是否有更好的方法?为什么JVM Clojure不允许间接引用异常类,但ClojureScript允许? 更新 在ClojureMostly的帮助下,我已将宏重构如下,并且它可以工作:
(defmacro my-macro
  [& forms]
 `(try
    ~@forms
    (catch ~(if (:ns &env) 'js/Error 'Exception) e#
      ,,,)))
2个回答

4
您可以将宏重构为函数调用的形式。该函数将接受一个“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
      ...)))

我太专注于解决问题了,甚至没有想到这个显而易见的解决方案!然而,ClojureMostly的答案更直接地回答了我的问题,所以我接受了它。 - grandinero

4
常见的做法是在 defmacro 中特殊的 &env 绑定中 检查 :ns。这个方法可以从 plumatic/schema 中复制(摘自 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))

1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - grandinero
1
我认为(检查&env中的:ns键)今天可以工作,甚至明天也可能工作。但是绝对没有保证它会工作。如果您实施此解决方案,则需要知道您依赖于“未记录的行为”。 - Nathan Davis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接