How to flatMap two Publishers with different Failure types in Combine
Asked Answered
W

1

6

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.

Weinshienk answered 3/10, 2019 at 20:41 Comment(4)
You probably want to use mapError in your pipelineFerreous
I mentioned that in the question, the other way would be to do something like mapError { DataTaskError.bang } to get the types to be the same but that feels hacky.Weinshienk
Sorry, I saw the but about mapping both to Error and missed your mention of mapError. I wouldn't map to Error rather I would have a case of ViewModelError that indicated there was an underlying DataTaskError. You then mapError from DataTaskError to ViewModelError. Your view model subscriber then only needs to know about ViewModelErrorFerreous
The problem is I won't have a DataTaskError yet, it's upset because the Never Failure type from trigger doesn't match the Failure type of dataTask. If that makes sense.Weinshienk
N
7

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()
}
Norven answered 4/10, 2019 at 19:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.