Reputation: 51
My goal is to chain multiple (two at this time) network calls with Combine, breaking chain if first call fails.
I have two object types: CategoryEntity
and SubcategoryEntity
. Every CategoryEntity
has a property called subcategoriesIDS
.
With first call I need to fetch all subcategories, with second I will fetch all categories and then I will create an array of CategoryEntityViewModel
.
CategoryEntityViewModel
contains an array of SubcategoryEntityViewModel
based on CategoryEntity
's subcategoriesIDS
.
Just to be clearer:
SubcategoryEntityViewModel
for every fetched subcategory and store somewhereCategoryEntityViewModel
is created for every category fetched. This object will be initialized with a CategoryEntity
object and an array of SubcategoryEntityViewModel
, found filtering matching ids between subcategoriesIDS
and stored SubcategoryEntityViewModel
arrayMy code right now is:
class CategoriesService: Service, ErrorManager {
static let shared = CategoriesService()
internal let decoder = JSONDecoder()
@Published var error: ServerError = .none
private init() {
decoder.dateDecodingStrategyFormatters = [ DateFormatter.yearMonthDay ]
}
func getAllCategories() -> AnyPublisher<[CategoryEntity], ServerError> {
let request = self.createRequest(withUrlString: "\(AppSettings.api_endpoint)/categories/all", forMethod: .get)
return URLSession.shared.dataTaskPublisher(for: request)
.receive(on: DispatchQueue.main)
.tryMap { data, response -> Data in
guard let httpResponse = response as? HTTPURLResponse, 200..<300 ~= httpResponse.statusCode else {
switch (response as! HTTPURLResponse).statusCode {
case (401):
throw ServerError.notAuthorized
default:
throw ServerError.unknown
}
}
return data
}
.map { $0 }
.decode(type: NetworkResponse<[CategoryEntity]>.self, decoder: self.decoder)
.map { $0.result}
.mapError { error -> ServerError in self.manageError(error: error)}
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
func getAllSubcategories() -> AnyPublisher<[SubcategoryEntity], ServerError> {
let request = self.createRequest(withUrlString: "\(AppSettings.api_endpoint)/subcategories/all", forMethod: .get)
return URLSession.shared.dataTaskPublisher(for: request)
.receive(on: DispatchQueue.main)
.tryMap { data, response -> Data in
guard let httpResponse = response as? HTTPURLResponse, 200..<300 ~= httpResponse.statusCode else {
switch (response as! HTTPURLResponse).statusCode {
case (401):
throw ServerError.notAuthorized
default:
throw ServerError.unknown
}
}
return data
}
.map { $0 }
.decode(type: NetworkResponse<[SubcategoryEntity]>.self, decoder: self.decoder)
.map { $0.result }
.mapError { error -> ServerError in self.manageError(error: error)}
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
}
These methods are working (sink
is called in another class, don't think it is useful so not copied here) but I cannot find the correct way to chain them.
Upvotes: 5
Views: 5577
Reputation: 534987
The way to chain async operations with Combine is flatMap
. Produce the second publisher inside the map function. Be sure to pass any needed info as a value down into the map function so the second publisher can use it. See How to replicate PromiseKit-style chained async flow using Combine + Swift for a basic example.
Upvotes: 2