使用线程宏的惯用语错误/异常处理

时间:2017-09-15 11:33:46

标签: clojure clojurescript

我使用http请求一次从一个API中获取数千个实体。作为管道中的下一步,我想将它们全部放入数据库中。

(->> ids
     (pmap fetch-entity)
     (pmap store-entity)
     (doall))

fetch-entity期望String id并尝试使用http请求检索实体,并返回Map或抛出异常(例如,因为超时)。

store-entity期望Map并尝试将其存储在数据库中。它可能会引发异常(例如,如果Map与数据库架构不匹配,或者它根本没有收到Map

Inelegant Error Handling

我的第一个“解决方案”是编写包装函数fetch-entity'store-entity'来捕获各自原始函数的异常。

fetch-entity'在失败时返回其输入,如果http请求失败,则基本上传递String id。这可以确保整个管道继续运输。

store-entity'检查其参数的类型。如果参数是Map(获取实体成功并返回Map),则会尝试将其存储在数据库中。

如果存储到数据库的尝试引发异常,或者store-entity'传递了String(id)而不是Map它将conj传递给外部Vector的{​​{1}}。

这样我以后可以使用error_ids来确定出现故障的频率以及哪些ID受到影响。

上述感觉不是达到我想要做的事情的明智之举。例如,我写error_ids的方式使用前一个流水线步骤(store-entity')来补充函数,因为它根据前一个流水线步骤是否成功而表现不同。

fetch-entity'了解名为store-entity'的外部Vector也感觉不对。

是否有一种惯用的方法来处理这种情况,你有多个管道步骤,其中一些可以抛出异常(例如,因为它们是I / O)我不能轻易使用谓词来确保函数将行为可预测,我不想打扰管道,只是稍后检查哪些情况出错?

2 个答案:

答案 0 :(得分:5)

可以使用Try monad类型,例如来自cats library

  

它表示可能导致异常或返回成功计算值的计算。与Either monad非常相似,但在语义上是不同的。

它由两种类型组成:成功和失败。 Success类型是一个简单的包装器,如Either Monad的Right。但是Failure类型与Left略有不同,因为它总是包装Throwable的实例(或者clj中的任何值,因为你可以在JavaScript主机中抛出任意值)。

(...)< br />
它是try-catch块的类比:它用基于堆的错误处理取代了try-catch的基于堆栈的错误处理。它不会抛出异常并且必须立即在同一个线程中处理它,而是会断开错误处理和恢复。

基于堆的错误处理是您想要的。

下面我举了一个fetch-entitystore-entity的例子。我让fetch-entity在第一个id(1)上抛出ExceptionInfostore-entity在第二个id(0)上抛出DivideByZeroException

(ns your-project.core
  (:require [cats.core :as cats]
            [cats.monad.exception :as exc]))


(def ids [1 0 2]) ;; `fetch-entity` throws on 1, `store-entity` on 0, 2 works


(defn fetch-entity
  "Throws an exception when the id is 1..."
  [id]
  (if (= id 1)
    (throw (ex-info "id is 1, help!" {:id id}))
    id))


(defn store-entity
  "Unfortunately this function still needs to be aware that it receives a Try.
  It throws a `DivideByZeroException` when the id is 0"
  [id-try]
  (if (exc/success? id-try)                 ; was the previous step a success?
    (exc/try-on (/ 1 (exc/extract id-try))) ; if so: extract, apply fn, and rewrap
    id-try))                                ; else return original for later processing


(def results
  (->> ids
       (pmap #(exc/try-on (fetch-entity %)))
       (pmap store-entity)))

现在,您可以分别使用resultssuccess?过滤成功或失败的failure?,并通过cats-extract

检索值
(def successful-results
  (->> results
       (filter exc/success?)
       (mapv cats/extract)))

successful-results ;; => [1/2]


(def error-messages
  (->> results
       (filter exc/failure?)
       (mapv cats/extract) ; gets exceptions without raising them
       (mapv #(.getMessage %))))

error-messages ;; =>  ["id is 1, help!" "Divide by zero"]

请注意,如果您只想按照以下方式使用传感器,则只需遍历errorssuccessful-results

(transduce (comp
            (filter exc/success?)
            (map cats/extract))
           conj
           results))
;; => [1/2]

答案 1 :(得分:1)

我的第一个想法是将fetch-entitystore-entity合并为一个操作:

(defn fetch-and-store [id]
  (try
    (store-entity (fetch-entity id))
    (catch ... <log error msg> )))

(doall (pmap fetch-and-store ids))

这样的事情会起作用吗?