我使用http请求一次从一个API中获取数千个实体。作为管道中的下一步,我想将它们全部放入数据库中。
(->> ids
(pmap fetch-entity)
(pmap store-entity)
(doall))
fetch-entity
期望String
id并尝试使用http请求检索实体,并返回Map
或抛出异常(例如,因为超时)。
store-entity
期望Map
并尝试将其存储在数据库中。它可能会引发异常(例如,如果Map
与数据库架构不匹配,或者它根本没有收到Map
。
我的第一个“解决方案”是编写包装函数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)我不能轻易使用谓词来确保函数将行为可预测,我不想打扰管道,只是稍后检查哪些情况出错?
答案 0 :(得分:5)
可以使用Try
monad类型,例如来自cats
library:
它表示可能导致异常或返回成功计算值的计算。与Either monad非常相似,但在语义上是不同的。
它由两种类型组成:成功和失败。 Success类型是一个简单的包装器,如Either Monad的Right。但是Failure类型与Left略有不同,因为它总是包装Throwable的实例(或者clj中的任何值,因为你可以在JavaScript主机中抛出任意值)。
(...)< br />
它是try-catch块的类比:它用基于堆的错误处理取代了try-catch的基于堆栈的错误处理。它不会抛出异常并且必须立即在同一个线程中处理它,而是会断开错误处理和恢复。
基于堆的错误处理是您想要的。
下面我举了一个fetch-entity
和store-entity
的例子。我让fetch-entity
在第一个id(1)上抛出ExceptionInfo
,store-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)))
现在,您可以分别使用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"]
请注意,如果您只想按照以下方式使用传感器,则只需遍历errors
或successful-results
:
(transduce (comp
(filter exc/success?)
(map cats/extract))
conj
results))
;; => [1/2]
答案 1 :(得分:1)
我的第一个想法是将fetch-entity
和store-entity
合并为一个操作:
(defn fetch-and-store [id]
(try
(store-entity (fetch-entity id))
(catch ... <log error msg> )))
(doall (pmap fetch-and-store ids))
这样的事情会起作用吗?