a) Why it fails
Your fetchAsync function returns an Async<Choice<_>> and you pattern match as if the function only returns Choice<_>. Unfortunately you can't pattern match on Async because that would be a blocking operation, exactly what Async tries move away from.
b) Idiomatic way of dealing with these things.
What you can do, however, is stay within the Async context and handle the failure within. F# provides (at least) two common ways to deal with this. E.g. you can use the async helper functions that allow you to write a pipeline:
let fetchAsyncPipeline (url: string) =
FSharp.Data.Http.AsyncRequestString(url)
|> Async.Catch
|> Async.map (function
| Choice1Of2 v -> Ok v
| Choice2Of2 e -> Error e.Message)
Unfortunately, Async.map is not yet included. But you can define it for yourself like this:
namespace global
[<RequireQualifiedAccess>]
module Async =
let map f xA =
async {
let! x = xA
return f x
}
Or, you can use F#'s computation expressions that provide syntactic sugar to write the above in a more imperative style:
let fetchAsyncCe (url: string) =
async {
try
return!
FSharp.Data.Http.AsyncRequestString(url)
|> Async.map Ok
with
| e -> return Error e.Message
}
In both these solutions, I converted the exception to F#'s result type, which I personally find the nicest way to deal with errors.
Finally, as indicated by brianberns, your expression is only wrapped in the Async type. But unlike C#'s Task, async computations represent the program how to calculate something, but that program has not yet started and you have to explicitly run the asynchronous operation. One of the ways to do this is to use Async.RunSynchronously:
Async.RunSynchronously (fetchAsyncCe "https://fsharpforfunandprofit.com/")
PS: You probably came across Scott Wlaschin's excellent F# for fun and profit, but if you didn't you should check it out. Personally, I've found it the best resource to teach you F# and functional programming in general.