问题描述
我有一个异步块,在这个块中,我从外部 C# Web 服务客户端库调用了一个异步方法。此方法调用返回数据传输对象,或类型为 ApiException
的自定义异常。最初,我的函数如下所示:
type Msg =
| LoginSuccess
| LoginError
| ApiError
let authUserAsync (client: Client) model =
async {
do! Async.SwitchToThreadPool()
let loginParams = new LoginParamsDto(Username = "some username",Password = "some password")
try
// AuthenticateAsync may throw a custom ApiException.
let! loggedInAuthor = client.AuthenticateAsync loginParams |> Async.AwaitTask
// Do stuff with loggedInAuthor DTO...
return LoginSuccess
with
| :? ApiException as ex ->
let msg =
match ex.StatusCode with
| 404 -> LoginError
| _ -> ApiError
return msg
}
但我发现 ApiException
没有被抓住。进一步调查显示,ApiException
实际上是内部异常。所以我把我的代码改成这样:
type Msg =
| LoginSuccess
| LoginError
| ApiError
let authUserAsync (client: Client) model =
async {
do! Async.SwitchToThreadPool()
let loginParams = new LoginParamsDto(Username = "some username",Password = "some password")
try
// AuthenticateAsync may throw a custom ApiException.
let! loggedInAuthor = client.AuthenticateAsync loginParams |> Async.AwaitTask
// Do stuff with loggedInAuthor DTO...
return LoginSuccess
with
| baseExn ->
let msg =
match baseExn.InnerException with
| :? ApiException as e ->
match e.StatusCode with
| 404 -> LoginError
| _ -> ApiError
| otherExn -> raise otherExn
return msg
}
这似乎有效。但是作为 F# 新手,我想知道在这种情况下是否有更优雅或更惯用的方法来捕获内部异常?
解决方法
我有时使用 active pattern 来捕获主要或内部异常:
let rec (|NestedApiException|_|) (e: exn) =
match e with
| null -> None
| :? ApiException as e -> Some e
| e -> (|NestedApiException|_|) e.InnerException
然后像这样使用它:
async {
try
...
with
| NestedApiException e ->
...
| otherExn ->
raise otherExn
}
,
基于 Tarmil 的活动模式的解决方案非常好,如果我想在文件或项目的多个位置捕获异常,我会使用它。但是,如果我只想在一个地方执行此操作,那么我可能不想为其定义单独的活动模式。
在 when
表达式中使用 try ... with
子句有一种更好的书写方式:
let authUserAsync (client: Client) model =
async {
try
// (...)
with baseExn when (baseExn.InnerException :? ApiException) ->
let e = baseExn :?> ApiException
match e.StatusCode with
| 404 -> return LoginError
| _ -> return ApiError }
这有点重复,因为您必须使用 :?
进行类型检查,然后再次使用 :?>
进行强制转换,但这是一种更好的内联方式。
作为替代方案,如果您使用 F# 异常,则可以使用适用于产品类型的全部模式匹配。错误处理代码读起来更简洁。
exception TimeoutException
exception ApiException of message: string * inner: exn
try
action ()
with
| ApiException (_,TimeoutException) ->
printfn "Timed out"
| ApiException (_,(:? ApplicationException as e)) ->
printfn "%A" e