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

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

我从 HTTP 调用中获得了一些数据列表。然后我知道要为另一个 HTTP 调用获取什么值。我想让一切都是异步的。但我需要将此数据与 Expecto's testCaseAsync : string -> Async<unit> -> Test 一起使用。所以,我的目标是获得这样的签名 Async<Item>[]

所以,我想获得 testCaseAsync.

的列表

所以,我基本上是这样的:

// 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
   }

如果我运行他们并行我得到:

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

所以,这并没有让我达到 Async<Item>[]。我不确定这是否可能。我想避免 Async.RunSynchronously 用于 API.getTable 调用,因为这会导致死锁,对吧?它很可能会从缓存值 (memoized) 中调用,所以我不确定这会有所作为。

我想我会继续努力,除非有人比我更聪明:-)提前致谢!

一般来说,你不能把Async<Async<T>[]>变成Async<T>[]。问题是即使要获取数组的长度,也需要异步执行一些操作,所以没有办法在异步之外 "lift" 数组。如果你事先知道数组的长度,那么你就可以做到这一点。

下面的函数将 Async<'T[]> 变成 Async<'T>[],前提是你给它数组的长度。如您所知,返回的异步需要以某种方式共享对一个顶级异步的访问。我能想到的最简单的方法是使用任务。为您的用例调整它应该很容易:

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] }  
  )