我试图在放弃异常之前多次执行一次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))))
答案 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
宏很优雅,但对于一次性内容,只需将when
从try
块中拉出来:
(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))