Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to flatMap two Publishers with different Failure types in Combine

I follow a pattern in my Rx code, I usually have an Observable trigger which I flatMap to create another Observable for a network request. A simplified example:

enum ViewModelError: Error {
  case bang
}

enum DataTaskError: Error {
  case bang
}

func viewModel(trigger: Observable<Void>,
               dataTask: Observable<Result<SomeType, DataTaskError>>) -> Observable<Result<AnotherType, ViewModelError>> {
  let apiResponse = trigger
    .flatMap { dataTask }
}

The Combine equivalent I'm having some trouble with. I could use a Result as the Output type and use Never as the Failure type but that feels like a misuse of the API.

func viewModel(trigger: AnyPublisher<Void, Never>,
               dataTask: AnyPublisher<SomeType, DataTaskError>) -> AnyPublisher<AnotherType, ViewModelError> {
  let apiResponse = trigger
    .flatMap { dataTask }
}

I get a compilation error:

Instance method 'flatMap(maxPublishers:_:)' requires the types 'Never' and 'DataTaskError' be equivalent

I could use mapError and cast both of the errors to Error, but I need a DataTaskError to be able to create my ViewModelError.

This feels like it shouldn't be so difficult, and it seems like a fairly common use case. I'm likely just misunderstanding some fundamentals, a point in the right direction would be greatly appreciated.

like image 642
mike Avatar asked Oct 03 '19 20:10

mike


1 Answers

When you have a publisher with Never as failure type, you can use setFailureType(to:) to match the failure type of another publisher. Note that this method can only be used when the failure type is Never, according to the doc. When you have an actual failure type you can convert the error with mapError(_:). So you can do something like this:

func viewModel(trigger: AnyPublisher<Void, Never>,
               dataTask: AnyPublisher<SomeType, DataTaskError>) -> AnyPublisher<AnotherType, ViewModelError> {
  trigger
    .setFailureType(to: ViewModelError.self) // Publisher<Void, ViewModelError>
    .flatMap {
        dataTask // Publisher<SomeType, DataTaskError>
            .mapError { _ in ViewModelError.bang } // Publisher<SomeType, ViewModelError>
            .map { _ in AnotherType() } // Publisher<AnotherType, ViewModelError>
    }
.eraseToAnyPublisher()
}
like image 92
Ernesto Badillo Avatar answered Nov 05 '22 20:11

Ernesto Badillo