haskell - 为什么 `catch` 没有捕捉到这个异常?
问题描述
我有一个仆人应用程序和一个在数据库中创建记录的端点,然后尝试在 S3 位置之间复制文件。如果复制失败,我想回滚事务。我有这个运营商
{-# LANGUAGE TemplateHaskell #-}
import Control.Monad.Catch
import Control.Monad.Except
import Control.Monad.Logger
(<??)
:: (MonadError e m, MonadCatch m, MonadLogger m)
=> e
-> m a
-> m a
(<??) err a = a `catchAll` (\e -> $(logErrorSH) e >> throwError err)
infixr 0 <??
捕获所有异常,记录异常的性质,然后抛出(在我的情况下,因为我的App
类型有一个实例MonadError ServantErr
) a ServantErr
。
我的处理程序是这样的:
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Monad
import Control.Monad.Catch
import Control.Monad.IO.Class
import qualified Network.AWS as AWS
import Servant
import App.Types
import App.Db
copy :: Copy -> App Text
copy (Copy user bucket srcKey tgtKey) = do
err400 <?? runDb (insertRecord $ User user bucket srcKey tgtKey)
catch (err500 <?? liftIO $ do
env <- AWS.newEnv AWS.Discover
void . AWS.runResourceT . AWS.runAWS env $ copyFiles bucket srcKey tgtKey
return "OK") (\(e :: ServantErr) -> rollback e user)
where rollback e u = runDb (deleteRecord u) >> throwError e
为了测试逻辑,我移动了我的 AWS 凭证文件,期望内部 AWS 操作会抛出一个InvalidFileError
,然后(<??)
将其转换为 a ServantErr
,然后catch
会捕获它ServantErr
并执行回滚功能。相反,插入成功,InvalidFileError
被记录,但回滚永远不会发生(即,执行后记录仍然存在于数据库中)。这个deleteRecord
函数在其他地方成功使用,所以我可以确定它的定义不是问题。
知道是什么原因造成的吗?
解决方案
如果您的App
类型最终是 a ExceptT
,则问题可能是 theMonadError
和MonadCatch
for 的实例ExceptT
不匹配:
- 该
MonadError
实例抛出错误e
作为ExceptT e
MonadCatch
实例捕获底层 monad 中的异常,而不是ExceptT e
错误。
的实例定义是MonadCatch (ExceptT e m)
:
-- | Catches exceptions from the base monad.
instance MonadCatch m => MonadCatch (ExceptT e m) where
catch (ExceptT m) f = ExceptT $ catch m (runExceptT . f)
ServantErr
有一个Exception
实例,所以它可以同时抛出。
编辑:“异常”类MonadMask
提供了onError
功能,即使 forExceptT
也表现得非常好:它在ExceptT e
异常和常规异常的情况下运行清理操作:
仅当主操作中引发错误时才运行操作。与 onException 不同,它适用于各种错误,而不仅仅是异常。例如,如果 f 是一个用 Left 中止的异常计算,计算 onError fg 将执行 g,而 onException fg 不会。
这是比catch
处理回滚更好的选择。
推荐阅读
- javascript - 平移时标记和轴标签随机不同步
- python - python错误中的随机密码生成器
- javascript - 使用 NodeJS 发送自动电子邮件
- javascript - 正则表达式捕获两种不同的格式
- python - 模型错误,TypeError: on_delete 必须是可调用的
- sql - 甲骨文 SQL。如何在“THEN”输出一个字母后跟 4 个数字的语句时创建一个案例
- powershell - 使用 RDP 登录时应用 GPO
- reactjs - FieldArray fields.push({ no, id }) - 没有属性可以显示,但 id 属性不能
- swift - SwiftUI 中 Picker 的增量范围
- gdb - 无法通过修改 EIP 寄存器执行系统功能