Chain network calls sequentially in Combine

Viewed 3399

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:

  1. Fetch subcategories
  2. Fetch categories
  3. Create a SubcategoryEntityViewModel for every fetched subcategory and store somewhere
  4. CategoryEntityViewModel 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 array

My 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.

1 Answers
Related