异步等待列表详细信息
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
}
}
我正在尝试通过多步异步调用获取详细的项目列表。首先我获取一个标识符列表,然后获取每个标识符的详细信息。
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
}
}