Developer11
Developer11

Reputation: 781

chaining async rest calls in a pipeline while managing errors

Building on previous question based on synchronous calls, how do we approach asynchronous methodology in the following scenario.

let fetch1 (result: string) : Result<string, string> =
    try
        use request = WebRequest.Create("http://bing.com") :?> HttpWebRequest
        use response = request.GetResponse()
        use reader = new StreamReader(response.GetResponseStream())
        let html = reader.ReadToEnd()
        Ok "success"
    with
        | :? WebException as e ->
        Error "error with the url"

let fetch2 (result: string) : Result<string, string> =
    try
        use request = WebRequest.Create("http://google.com") :?> HttpWebRequest
        use response = request.GetResponse()
        use reader = new StreamReader(response.GetResponseStream())
        let html = reader.ReadToEnd()
        Ok "success"
    with
        | :? WebException as e ->
        Error "error with the url"

let fetch3 (result: string) : Result<string, string> =
     try
         use request = WebRequest.Create("http://invalid.com") :?> HttpWebRequest
         use response = request.GetResponse()
         use reader = new StreamReader(response.GetResponseStream())
         let html = reader.ReadToEnd()
         Ok "success"
     with
         | :? WebException as e ->
         Error "error with the url"

Test

let chain = fetch1 >> Result.bind fetch2 >> Result.bind fetch3

match chain("") with 
| Ok message -> Debug.WriteLine(message)
| Error error -> Debug.WriteLine(error)

Attempt

let fetch1 (result: string) :Result<string, string> = async {
    try
        use! request = WebRequest.Create("http://bing.com") :?> HttpWebRequest
        use response = request.GetResponse()
        use reader = new StreamReader(response.GetResponseStream())
        let html = reader.ReadToEnd()
        Ok "success"
    with
        | :? WebException as e ->
        Error "error with the url"
 }

Error

This expression was epxected to have type 'Result' but here has type 'Async<'a>'

Upvotes: 1

Views: 73

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243061

You should explain what exception handling mechanism are you actually trying to implement here. For many things, using just normal F# built-in exceptions will work perfectly fine.

You are using Result.bind which is a way to implement an exception handling behaviour where, if any step throws an exception, the overall computation will fail. Is this what you want to achieve? If so, you can just rely on built-in F# async support for exception handling and have try .. with in the async computation that calls the two fetch functions. A minimal example:

let fetch url = async {
  use wc = new WebClient()
  let! res = wc.AsyncDownloadString(System.Uri(url))
  return res }

let fetchAllOrNothing = async {
  try 
    let! goog = fetch "http://www.google.com"
    let! bing = fetch "http://www.bing.com"
    return Some(goog.Length, bing.Length)
  with _ ->
    return None }

If you want to call all fetch functions and fail only if all of them fail, then you will need to wrap individual fetch functions with exception handlers (as you do) and then pick the first result. Using normal F# option type, this can be done using Array.tryPick:

let fetch url = async {
  try
    use wc = new WebClient()
    let! res = wc.AsyncDownloadString(System.Uri(url))
    return Some res 
  with _ -> 
    return None }

let fetchFirstOne = async {
  let! all = 
    [ fetch "http://www.google.com"
      fetch "http://www.bing.com" ] |> Async.Parallel
  return Array.tryPick (fun x -> x) all }

If you are starting with F#, then it is a lot easier to get started using things like core async functions and options - once you are comfortable with those, it makes sense to look at more sophisticated methods.

Upvotes: 2

Related Questions