12

I'm new to F# and stuck in understanding async in F# from the perspective of a C# developer. Say having the following snippet in C#:

var httpClient = new HttpClient();
var response = await httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
string content = await response.Content.ReadAsStringAsync();

How to write the same in F#?

abatishchev
  • 98,240
  • 88
  • 296
  • 433

5 Answers5

19

Here is a function that should do what you're looking for (note that you'll have to wrap the code in an asynchronous computation expression in order to use the let! syntax):

let getAsync (url:string) = 
    async {
        let httpClient = new System.Net.Http.HttpClient()
        let! response = httpClient.GetAsync(url) |> Async.AwaitTask
        response.EnsureSuccessStatusCode () |> ignore
        let! content = response.Content.ReadAsStringAsync() |> Async.AwaitTask
        return content
    }
Michael Bowersox
  • 1,471
  • 11
  • 17
  • 6
    shouldn't the first two `let`s be `use`s instead? (httpClient & response being IDisposable) – Cogwheel Feb 08 '16 at 22:34
  • Probably no, `HttpClient` is [special in that](https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/) – psfinaki May 03 '20 at 19:40
8

You'll want to at least read and be aware of the established patterns in Http.fs if you're doing anything with HttpClient in F#.

[See comments] TL;DR ... but Beware the Share. As noted by @pimbrouwers, it should be noted that you don't necessarily have to then use it though - subsetting and/or evolving your own set of helpers in your context can lead you to a better fitting abstraction (and bring you the benefits of the learning on the way).

To this point: It's considered idiomatic practice in F# to keep rarely used and/or overly specific helpers in a non-central location.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
  • 1
    No disrespect intended. But I find answers referring someone directly to a library to be such a cop out. Sure, inevitably you may end up using it. But from my perspective you should always journey there yourself first. Otherwise we'll end up with a bunch of programmers who've become so removed from what they're actually trying to do, that they can only accomplish their jobs using external library's. A perfect example of this is ADO.NET. You would be SHOCKED how many .NET developers I've made that wouldn't know the first thing about brokering a `SqlConnection` and executing a `SqlCommand`. – pim Aug 16 '18 at 19:42
  • 1
    Point being, try it yourself first. Make mistakes. Screw it up royally (I know I always do). But actually LEARN something in the process. – pim Aug 16 '18 at 19:43
  • 1
    I somewhat agree; I would not write the same answer today. However my point about not writing F# wrapping for `HttpClient` without being aware of a library in widespread use remains - for example sticking with the same naming for the same concepts etc. Will edit to reflect that. – Ruben Bartelink Aug 16 '18 at 21:30
6

You can use async:

let readString (url: Uri) = async {
    let httpClient = new HttpClient();
    let! response = httpClient.GetAsync(url) |> Async.AwaitTask
    response.EnsureSuccessStatusCode() |> ignore
    return! response.Content.ReadAsStringAsync() |> Async.AwaitTask
}
Lee
  • 142,018
  • 20
  • 234
  • 287
2

Just my two cents. But my understanding is that we should be handling the HttpRequestException when using EnsureSuccessStatusCode().

Below is the start of a module wrapping HttpClient that will buffer the response of a URL into a string and safely wrap in a Result<'a, 'b> for improved fault tolerance.

module Http =
    open System    
    open System.Net.Http

    let getStringAsync url =
        async {
            let httpClient = new HttpClient() 
            // This can be easily be made into a HttpClientFactory.Create() call
            // if you're using >= netcore2.1

            try 
                use! resp = httpClient.GetAsync(Uri(url), HttpCompletionOption.ResponseHeadersRead) |> Async.AwaitTask                       
                resp.EnsureSuccessStatusCode |> ignore

                let! str = resp.Content.ReadAsStringAsync() |> Async.AwaitTask
                return Ok str
            with
            | :? HttpRequestException as ex -> return ex.Message |> Error
        }
pim
  • 12,019
  • 6
  • 66
  • 69
1

Just use FSharp.Control.FusionTasks and you will have clear syntax without |> Async.AwaitTask like

   let httpClient = new System.Net.Http.HttpClient ()

   let getAsync (url:string) = 
    async {
        let! response = httpClient.GetAsync url
        response.EnsureSuccessStatusCode () |> ignore
        let! content = response.Content.ReadAsStringAsync ()
        return content
    }
Andrii
  • 1,081
  • 1
  • 11
  • 24