1

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?

3 Answers 3

4

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
}
Sign up to request clarification or add additional context in comments.

4 Comments

I like this, but would use reraise instead of raise otherExn.
You can't use reraise in an async though. But come to think of it, you can also just skip this branch and it will be reraised by async.
Interesting. I didn’t know that.
Thank you all for the input. In my situation I probably will be needing to catch this exception in multiple places, so I'm going to go with @Tarmil for this one.
3

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.

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.