Philip K.
Philip K.

Reputation: 51

Chain network calls sequentially in Combine

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.

Upvotes: 5

Views: 5577

Answers (1)

matt
matt

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

Related Questions