简体   繁体   中英

Catching inner exception in F# async block

I have an async block, within this block I call an async method from an external C# web service client library. This method call returns a data transfer object, or a custom exception of type ApiException . Initially, my function looked like this:

    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
        }

But I found that the ApiException wasn't being caught. Further investigation revealed that the ApiException was in fact the inner exception. So I changed my code to this:

    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
        }

Which seems to work. But being new to F# I'm wondering if there is there a more elegant or idiomatic way to catch an inner exception in this kind of situation?

I sometimes use an active pattern to catch the main or the inner exception:

let rec (|NestedApiException|_|) (e: exn) =
    match e with
    | null -> None
    | :? ApiException as e -> Some e
    | e -> (|NestedApiException|_|) e.InnerException

and then use it like this:

async {
    try
        ...
    with
    | NestedApiException e ->
        ...
    | otherExn ->
        raise otherExn
}

The solution based on active patterns from Tarmil is very nice and I would use that if I wanted to catch the exception in multiple places across a file or project. However, if I wanted to do this in just one place, then I would probably not want to define a separate active pattern for it.

There is a somewhat nicer way of writing what you have using the when clause in the try... with expression:

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 }

This is somewhat repetitive because you have to do a type check using :? and then again a cast using :?> , but it is a bit nicer inline way of doing this.

As an alternative, if you use F# exceptions, you can use the full range of pattern matching available for product types. Error handling code reads a lot more succinctly.

exception TimeoutException
exception ApiException of message: string * inner: exn

try
    action ()
with
| ApiException (_, TimeoutException) ->
    printfn "Timed out"
| ApiException (_, (:? ApplicationException as e)) ->
    printfn "%A" e

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM