我正在尝试将n
请求与Combine
链接。
假设我有50个用户,每个用户我需要做一个请求来获取用户数据。我知道使用flatMap
可以将一个Publisher
结果传递到下一个。但这对循环也有效吗?
这是我获取用户的功能:
func fetchUser(for id: Int) -> AnyPublisher<User, Error> {
let url = "https://user.com/api/user/(id)"
return URLSession.shared.dataTaskPublisher(for: url)
.mapError { $0 as Error }
.map { $0.data }
.decode(type: User.self, decoder: JSONDecoder())
.eraseToAnyPublisher()
}
所以基本上我需要另一个函数,它在这个fetchUser
上循环并返回一个结果数组中的所有用户。请求不应该同时运行,而应该在上一个请求完成后启动一个请求。
对于这个,很大程度上取决于您想要如何使用User对象。如果您希望它们在进入时都作为单个用户发射,那么merge
就是解决方案。如果您想保持数组的顺序,并在所有用户进入后将其作为一个用户数组发出,那么combineLatest
就是您所需要的。
由于您处理的是数组,而merge和combineLatest都没有数组版本,因此需要使用reduce。以下是示例:
func combine(ids: [Int]) -> AnyPublisher<[User], Error> {
ids.reduce(Optional<AnyPublisher<[User], Error>>.none) { state, id in
guard let state = state else { return fetchUser(for: id).map { [$0] }.eraseToAnyPublisher() }
return state.combineLatest(fetchUser(for: id))
.map { $0.0 + [$0.1] }
.eraseToAnyPublisher()
}
?? Just([]).setFailureType(to: Error.self).eraseToAnyPublisher()
}
func merge(ids: [Int]) -> AnyPublisher<User, Error> {
ids.reduce(Optional<AnyPublisher<User, Error>>.none) { state, id in
guard let state = state else { return fetchUser(for: id).eraseToAnyPublisher() }
return state.merge(with: fetchUser(for: id))
.eraseToAnyPublisher()
}
?? Empty().eraseToAnyPublisher()
}
请注意,在组合的情况下,如果数组为空,则发布服务器将发出一个空数组,然后完成。在合并的情况下,它将在不发出任何东西的情况下完成。
还要注意,在任何一种情况下,如果任何一个发布服务器失败,那么整个链都将关闭。如果你不想这样,你就必须抓住错误并采取措施。。。