Clojure:如何重复异常?

时间:2009-12-10 09:44:24

标签: clojure

我试图在放弃异常之前多次执行一次func。 但是在Clojure中从catch块中重现是无效的。 怎么能实现这一目标?

(loop [tries 10]
  (try
    (might-throw-exception)
    (catch Exception e
      (when (pos? tries) (recur (dec tries))))))

java.lang.UnsupportedOperationException: Cannot recur from catch/finally 

我能找到的最好的是以下笨拙的解决方案(包装在func中并调用它)

(defn do-it []
  (try
    (might-throw-exception)
    (catch Exception e nil)))

(loop [times 10]
  (when (and (nil? (do-it)) (pos? times))
    (recur (dec times))))

8 个答案:

答案 0 :(得分:45)

宏正在调用......

这个怎么样:

(defn try-times*
  "Executes thunk. If an exception is thrown, will retry. At most n retries
  are done. If still some exception is thrown it is bubbled upwards in
  the call chain."
  [n thunk]
  (loop [n n]
    (if-let [result (try
                      [(thunk)]
                      (catch Exception e
                        (when (zero? n)
                          (throw e))))]
      (result 0)
      (recur (dec n)))))

(defmacro try-times
  "Executes body. If an exception is thrown, will retry. At most n retries
  are done. If still some exception is thrown it is bubbled upwards in
  the call chain."
  [n & body]
  `(try-times* ~n (fn [] ~@body)))

答案 1 :(得分:12)

kotarak的想法是要走的路,但是这个问题让我感到奇怪,所以我想提供一个关于我喜欢的相同主题的riff,因为它不使用loop / recur:

(defn try-times* [thunk times]
  (let [res (first (drop-while #{::fail}
                               (repeatedly times
                                           #(try (thunk)
                                                 (catch Throwable _ ::fail)))))]
    (when-not (= ::fail res)
      res)))

保持try-times宏不变。

如果你想允许thunk返回nil,你可以删除let / when对,let :: fail代表“函数失败了n次”,而nil意味着“函数返回nil”。这种行为会更灵活但不太方便(调用者必须检查:: fail以查看它是否有效而不仅仅是nil),所以最好将它作为可选的第二个参数实现:

(defn try-times* [thunk n & fail-value]
  (first (drop-while #{fail-value} ...)))

答案 2 :(得分:4)

我的建议:

(defmacro try-times
  "Retries expr for times times,
  then throws exception or returns evaluated value of expr"
  [times & expr]
  `(loop [err# (dec ~times)]
     (let [[result# no-retry#] (try [(do ~@expr) true]
                   (catch Exception e#
                     (when (zero? err#)
                       (throw e#))
                     [nil false]))]
       (if no-retry#
         result#
         (recur (dec err#))))))

一次打印“没有错误”:

(try-times 3 (println "no errors here") 42)

将打印“尝试”3次,然后将除以零:

(try-times 3 (println "trying") (/ 1 0))

答案 3 :(得分:1)

try-times宏很优雅,但对于一次性内容,只需将whentry块中拉出来:

(loop [tries 10]
  (when (try
          (might-throw-exception)
          false ; so 'when' is false, whatever 'might-throw-exception' returned
          (catch Exception e
            (pos? tries)))
    (recur (dec tries))))

答案 4 :(得分:0)

另一个解决方案,没有宏

(defn retry [& {:keys [fun waits ex-handler]
                :or   {ex-handler #(log/error (.getMessage %))}}]
  (fn [ctx]
    (loop [[time & rem] waits]
      (let [{:keys [res ex]} (try
                               {:res (fun ctx)}
                               (catch Exception e
                                 (when ex-handler
                                   (ex-handler e))
                                 {:ex e}))]
        (if-not ex
          res
          (do
            (Thread/sleep time)
            (if (seq rem)
              (recur rem)
              (throw ex))))))))

答案 5 :(得分:0)

这不仅可以捕获多个异常,而且可以提供有关重试原因的一些反馈。

(defmacro try-n-times
  "Try running the body `n` times, catching listed exceptions."
  {:style/indent [2 :form :form [1]]}
  [n exceptions & body]
  `(loop [n# ~n
          causes# []]
     (if (> n# 0)
       (let [result#
             (try
               ~@body
               ~@(map (partial apply list 'catch) exceptions (repeat `(e# e#))))]
         (if (some #(instance? % result#) ~exceptions)
           (recur (dec n#) (conj causes# result#))
           result#))
       (throw (ex-info "Maximum retries exceeded!"
                       {:retries ~n
                        :causes causes#})))))

答案 6 :(得分:0)

如果向循环中添加 result 参数,则可以将 (try) 块嵌套在 (recur) 内。我是这样解决的:

(loop [result nil tries 10]
  (cond (some? result) result
        (neg? tries) nil
        :else (recur (try (might-throw-exception)
                          (catch Exception e nil))
                     (dec tries))))

答案 7 :(得分:0)

这是另一种方法:

(loop [tries 10]
  (let [res (try
              (might-throw-exception)
              (catch Exception e
                (if (pos? tries)
                  ::retry
                  (throw e))))]
    (if (#{::retry} res)
      (recur (dec tries))
      res)))

但是我还可以推荐一个很酷的小技巧,而不是多次重试,而是提供一系列睡眠时间:

(loop [tries [10 10 100 1000]]
  (let [res (try
              (might-throw-exception)
              (catch Exception e
                (if tries
                  ::retry
                  (throw e))))]
    (if (#{::retry} res)
      (do
        (Thread/sleep (first tries))
        (recur (next tries)))
      res)))

如果您希望它不那么冗长,最后将其全部放入一个宏中:

(defmacro with-retries
  [retries & body]
  `(loop [retries# ~retries]
     (let [res# (try ~@body
                     (catch Exception e#
                       (if retries#
                         'retry#
                         (throw e#))))]
       (if (= 'retry# res#)
         (do (Thread/sleep (first retries#))
             (recur (next retries#)))
         res#))))

(with-retries [10 10 100 1000]
  (might-throw-exception))