I am trying to chain n
requests with Combine
.
Let's assume I have 50 users and for each of them I need to do a single request to get a users data. I know that with flatMap
you can pass one Publisher
result into the next. But does that work with loops as well?
That's my function to fetch a user:
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()
}
So basically I need another function, which loops this over this fetchUser
and returns all users in one result array. The requests should not all run at the same time, but rather start one after the previous one has finished.
For this one, a lot depends on how you want to use the User objects. If you want them to all emit as individual users as they come in, then merge
is the solution. If you want to keep the array order and emit them all as an array of users once they all come in, then combineLatest
is what you need.
Since you are dealing with an array, and neither merge nor combineLatest have array versions, you will need to use reduce. Here's examples:
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()
}
Notice that in the combine case, if the array is empty, the Publisher will emit an empty array then complete. In the merge case, it will just complete without emitting anything.
Also notice that in either case if any of the Publishers fail, then the entire chain will shut down. If you don't want that, you will have to catch the errors and do something with them...