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

tos*_*osh 4 clojure clojurescript

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

(->> ids
     (pmap fetch-entity)
     (pmap store-entity)
     (doall))
Run Code Online (Sandbox Code Playgroud)

fetch-entity期望一个Stringid并尝试使用http请求检索实体,并返回Map或抛出异常(例如,由于超时).

store-entity期望a Map并尝试将其存储在数据库中.它可能会抛出异常(例如,如果Map它与数据库模式不匹配,或者它根本没有收到Map).

优雅的错误处理

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

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

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

如果存储到数据库的尝试抛出一个异常,或者如果store-entity'得到通过的String(ID),而不是Mapconj外部Vectorerror_ids.

这样我以后可以error_ids用来计算出故障的频率以及哪些ID受到影响.

它并不觉得上述是实现我想要做的事情的明智方式.例如,我编写的方式store-entity'使用前面的管道步骤(fetch-entity')来补充函数,因为它根据先前的管道步骤是否成功而表现不同.

同时store-entity'了解外部Vector呼叫error_ids也感觉不对.

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

Erw*_*ers 5

可以使用一种Trymonad,例如来自cats:

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

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

(...)

它是try-catch块的类比:它用基于堆的错误处理替换try-catch的基于堆栈的错误处理.它不是抛出异常而是必须立即在同一个线程中处理它,它会断开错误处理和恢复.

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

下面我做了一个例子fetch-entitystore-entity.余由fetch-entity抛出ExceptionInfo在第一ID(1)和store-entity抛出一个DivideByZeroException在所述第二ID(0).

(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)))
Run Code Online (Sandbox Code Playgroud)

现在,您可以过滤results对成功或失败,分别success?或者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"]
Run Code Online (Sandbox Code Playgroud)

请注意,如果您只想循环使用errorssuccessful-results一次使用传感器,如下所示:

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