Search code examples
exceptionf#computation-expression

How to implement non-nested exception handling on each step in an F# task computation expression?


Given the F# task computation expression I can write:-

task {
    try
        let! accessToken = getAccessTokenAsync a b

        try
            let! resource = getResourceAsync accessToken uri
            // do stuff
        with
            | ex -> printfn "Failed to get API resource.  %s" ex.Message
    with
        | ex -> printfn "Failed to get access token.  %s" ex.Message

    return ()
}

but what I want to do is have non-nested exception handling around the two getBlahAsync function calls. This can be done in C# quite easily in an async method with multiple awaits.

How to do so in an F# computation expression? If I try it in the simple and obvious way, accessToken from the first try..with doesn't flow into the second try..with.

(The trouble with nesting is that the // do stuff section could grow a bit, pushing the outer with further and further away from its try.)

How to do it in C#:-

static async Task MainAsync()
{
    String accessToken = null;
    try
    {
        accessToken = await GetAccessTokenAsync("e", "p");
    }
    catch (Exception ex)
    {
        Console.Error.WriteLine("Failed to get access token.  " + ex.Message);
        return;
    }

    String resource = null;
    try
    {
        resource = await GetResourceAsync(accessToken);
    }
    catch (Exception ex)
    {
        Console.Error.WriteLine("Failed to get API resource.  " + ex.Message);
        return;
    }

    // do stuff
}

Solution

  • The main problem with translating the C# code is that F# does not let you use return to jump out of the function body early. You can avoid nesting exceptions in various ways, but you will not be able to return early. This can be implemented as another computatione expression, but that's more of a curiosity than something you'd actually want to use here.

    My recommendation would be to just split the function into one that gets all the resources and handles exceptions and another one that does the stuff. That does not eliminate nesting, but it will make the code fairly readable.

    let doStuff accessToken resource = task {
      // do stuff
    }
    
    let getResourcesAndDoStuff a b uri = task {
      try
        let! accessToken = getAccessTokenAsync a b
        try
          let! resource = getResourceAsync accessToken uri
          return! doStuff accessToken resource
        with ex -> 
          printfn "Failed to get API resource.  %s" ex.Message
      with ex ->
        printfn "Failed to get access token.  %s" ex.Message 
    }
    

    As an aside, do you have some particular reason for using task rather than the normal built-in F# async workflow? It is not necessarily a problem, but async composes better and supports cancellation, so it is often a sensible default choice.