异步等待列表详细信息

Async await list detail

我正在尝试通过多步异步调用获取详细的项目列表。首先我获取一个标识符列表,然后获取每个标识符的详细信息。

func fetchList() async -> [UUID] {
    // some async network call here
    await Task.sleep(2)
    return [UUID(), UUID(), UUID()]
}

func fetchDetail(forUUID uuid: UUID) async -> String {
    // some async network call here
    await Task.sleep(1)
    return "\(uuid.uuidString.reversed())"
}

这些功能单独使用时效果很好,但是当我想尝试使用列表中的 .map 时,我卡住了。

Task {
    let list = await fetchList()
    let details = await list.map { fetchDetail(forUUID: [=11=]) } // Does not work
}

我确实找到了 withTaskGroup 函数,我当然可以异步附加到项目数组,但我想知道是否有更好的方法来实现此功能。

异步代码地图需要 AsyncSequence 才能工作。

struct DelayedUUIDs: AsyncSequence {
  typealias Element = UUID

  struct DelayedIterator: AsyncIteratorProtocol {
    private var internalIterator = [UUID(), UUID(), UUID()].makeIterator()

    mutating func next() async -> UUID? {
      await Task.sleep(1_000_000_000)
      return internalIterator.next()
    }
  }

  func makeAsyncIterator() -> DelayedIterator {
    DelayedIterator()
  }
}

func fetchList() async -> DelayedUUIDs {
    // some async network call here
    await Task.sleep(1)
    return DelayedUUIDs()
}

func fetchDetail(forUUID uuid: UUID) async -> String {
    // some async network call here
    await Task.sleep(1)
    return "ID: \(uuid.uuidString)"
}


Task {
    let list = await fetchList()
    let details = list.map({ id in await fetchDetail(forUUID: id) })
    for await value in list {
      print(Date())
      print(value)
    }
    for await value in details {
      print(Date())
      print(value as String)
    }
}

UPD:通用 AsyncSequence 示例。

struct DelayedArr<E>: AsyncSequence, AsyncIteratorProtocol {
    typealias Element = E
    var current = 0
    var elements: [E]
    mutating func next() async -> E? {
        defer { current += 1 }
        await Task.sleep(1_000_000_000)
        guard current < elements.count else { return nil }
        return elements[current]
    }

    func makeAsyncIterator() -> DelayedArr {
        self
    }
}

let sequence = DelayedArr(elements: [1, 2, 3 ,4, 5])
func asyncIntToString(i: Int) async -> String {
    await Task.sleep(2_000_000_000)
    return String(i)
}

let asyncMapSequence1 = sequence.map{ await asyncIntToString(i: [=11=])} //asyncronous code
let asyncMapSequence2 = sequence.map{ String([=11=]) } // or syncronous code

Task {
    for await value in asyncMapSequence1 {
        print(Date())
        print(value)
    }
    for await value in asyncMapSequence2 {
        print(Date())
        print(value)
    }
}

在您实际给出的示例中,fetchList returns 一组值,一次全部。因此,它是异步的这一事实实际上是无关紧要的。关键是我们现在从一个数组开始,我们希望通过调用异步方法来处理它的元素。

因此,您基本上有两种选择:您可以一个接一个地处理元素,等待每个元素,或者您可以并发处理它们。如果你想同时处理,那就是task group的作用,而且你都说自己知道了,就不用多说了。这正是 WWDC 视频以及我在 https://www.biteinteractive.com/swift-5-5-asynchronous-looping-with-async-await/.

上提供的有关此主题的示例。

请注意,正如视频和我的教程中所指出的那样,如果您选择同时处理这些值,结果可以按任何顺序返回,因此如果您不这样做,则必须采取一些额外的步骤不想失去每个原始 URL 与其处理结果之间的联系。通常的解决方案是构造一个字典或只是一个元组对。在我的书中,我使用字典方法。改编自该代码,你可以这样做:

func processManyURLs(urls: [URL]) async -> [URL:String] {
    return try await withTaskGroup(
        of: [URL:String].self, 
        returning: [URL:String].self) { group in
            var result = [URL:String]()
            for url in urls {
                group.addTask {
                    return [url: await fetchDetail(url: url)]
                }
            }
            for try await d in group {
                result.merge(d) {cur,_ in cur}
            }
            return result
    }
}