Nested Async Contexts in F#

Viewed 129

I have to make several calls to an API over the network.

let RestCall1 args = async{ Thread.Sleep (1000); return args}
let RestCall2 args = async{Thread.Sleep (1000); return args} 

I know that an async{...} context has a performance hit, so I'm trying to minimize the number of contexts I create. Plus it's awkward to pipeline.

let nested4 args = async{ let! x = RestCall1 args; return args }
let nested7 args = async{ let! x = RestCall2 args; return args }

let outerAsync (args : string) =
    async {
        let args1 = args
                    |> nested1
                    |> nested2
                    |> nested3
        let! args2 = nested4 args1
        let args3 = args2
                    |> nested5
                    |> nested6
        return nested7 args3          
    }

Is there a way of doing it (like the sample below) without blocking the thread with RunSynch?

let nested4 args = RestCall1 args |> Async.RunSynchronously
let nested7 args = RestCall2 args |> Async.RunSynchronously

let outerAsync (args : string) =
    async {
        return
            args
            |> nested1
            |> nested2
            |> nested3
            |> nested4
            |> nested5
            |> nested6
            |> nested7 
    }
1 Answers

You are correct in wanting to avoid Async.Runsynchronously, which will block (see "Does Async.RunSynchronously block?").

As TheQuickBrownFox suggests, you want a library that supplies Async.map and Async.bind. Here I have used FsToolkit.ErrorHandling:

  open System.Threading
  open FsToolkit.ErrorHandling

  let restCall1 args = async{ Thread.Sleep (1000); return args }
  let restCall2 args = async{ Thread.Sleep (1000); return args }

  let nested1 args = args
  let nested2 args = args
  let nested3 args = args
  let nested4 args = restCall1 args
  let nested5 args = args
  let nested6 args = args
  let nested7 args = restCall2 args

  let outerAsync (args : string) : Async<string> =
    args
    |> nested1
    |> nested2
    |> nested3
    |> nested4
    |> Async.map nested5
    |> Async.map nested6
    |> Async.bind nested7

Async.map takes nested5 and changes its signature from (string -> string) to (Async<string> -> Async<string>). It is called a functor.

Async.bind takes nested7 and changes its signature from (string -> Async<string>) to (Async<string> -> Async<string>).

Related