我同意 Matt 的观点,即您应该考虑返回一个字典,它与顺序无关,但提供 O(1) 的结果检索。我可能会建议一个稍微更简洁的实现:
func fetchPosts(ids: [Int]) async throws -> [Int: Post] {
try await withThrowingTaskGroup(of: (Int, Post).self) { group in
for id in ids {
group.addTask { try await (id, self.fetchPost(id: id)) }
}
return try await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
}
}
或者,如果 Post 符合 Identifiable,则不再需要元组 kruft:
func fetchPosts(ids: [Post.ID]) async throws -> [Post.ID: Post] {
try await withThrowingTaskGroup(of: Post.self) { group in
for id in ids {
group.addTask { try await self.fetchPost(id: id) }
}
return try await group.reduce(into: [:]) { $0[$1.id] = $1 }
}
}
如果你想返回[Post],只需从字典中构建数组:
func fetchPosts(ids: [Post.ID]) async throws -> [Post] {
try await withThrowingTaskGroup(of: Post.self) { group in
for id in ids {
group.addTask { try await self.fetchPost(id: id) }
}
let dictionary = try await group.reduce(into: [:]) { $0[$1.id] = $1 }
return ids.compactMap { dictionary[$0] }
}
}
您的实施可能会有所不同,但希望这说明了另一种模式。
顺便说一句,如果你经常这样做,你可以定义一个 Sequence 扩展来为你做这件事,例如:
extension Sequence where Element: Sendable {
@inlinable public func throwingAsyncValues<T>(
of type: T.Type = T.self,
body: @escaping @Sendable (Element) async throws -> T
) async rethrows -> [T] {
try await withThrowingTaskGroup(of: (Int, T).self) { group in
for (index, value) in enumerated() {
group.addTask { try await (index, body(value)) }
}
let dictionary = try await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
return enumerated().compactMap { dictionary[$0.0] }
}
}
}
用法如下:
func fetchPosts(ids: [Post.ID]) async throws -> [Post] {
try await ids.throwingAsyncValues { id in
try await self.fetchPost(id: id)
}
}
显然,您也可以轻松制作非throwing 再现,但希望这能说明扩展的基本思想,以简化调用点。