Search code examples
asynchronousf#expecto

F# Make Async<Async<MyTpe>[]> to Async<MyType>[]


I get some list of data from a HTTP call. I then know what values to get for another HTTP call. I would like to have everything be asynchronous. But I need to use this data with Expecto's testCaseAsync : string -> Async<unit> -> Test. So, my goal is to get a signature like so Async<Item>[]

So, I would like to get a list of testCaseAsync.

So, I basically have something like this:

// Async<Async<Item>[]>
let getAsyncCalls =
   async {
       let! table = API.getTable ()
       // Async<Item>[]
       let items =
           table.root
           |> Array.map (fun x -> API.getItem x.id)
       return item
   }

If I run them in parallel I get:

// Async<Item[]>
let getAsyncCalls =
   async {
       let! table = API.getTable ()
       // Item[]
       let! items =
           table.root
           |> Array.map (fun x -> API.getItem x.id)
       return item
   }

So, that doesn't get me to Async<Item>[]. I'm not sure if this is possible. I would like to avoid Async.RunSynchronously for the API.getTable call since that can lead to deadlocks, right? It will most likely be called from a cached value (memoized) so I'm not sure that will make a difference.

I guess I'll keep working on it unless someone else is more clever than me :-) Thanks in advance!


Solution

  • In general, you cannot turn Async<Async<T>[]> into Async<T>[]. The problem is that to even get the length of the array, you need to perform some operation asynchronously, so there is no way to "lift" the array outside of the async. If you knew the length of the array in advance, then you can make this work.

    The following function turns Async<'T[]> into Async<'T>[] provided that you give it the length of the array. As you figured out, the returned asyncs need to somehow share access to the one top-level async. The easiest way of doing this I can think of is to use a task. Adapting that for your use case should be easy:

    let unwrapAsyncArray (asyncs:Async<'T[]>) len = 
      let task = asyncs |> Async.StartAsTask
      Array.init len (fun i -> async {
        let! res = Async.AwaitTask task
        if res.Length <> len then failwith "Wrong length!"
        return res.[i] }  
      )