Search code examples
swiftcombine

How to use CombineLates when when publishers failure types is not equivalent?


I have two functions which return AnyPublisher with different failure type: Never and Error. When using these functions in the CombineLates, then compilation fails with an error: Generic struct 'CombineLatest' requires the types 'Error' and 'Never' be equivalent

Function which never fails:

func foo() -> AnyPublisher<Int, Never> {
    Result<Int, Never>
        .success(1).publisher
        .eraseToAnyPublisher()
}

Function which sometimes fails:

func boo() -> AnyPublisher<Int, Error> {
    Result<Int, Error>
        .failure(NSError(domain: "d", code: -1))
        .publisher.eraseToAnyPublisher()
}

foo & boo functions usage:

Publishers.CombineLatest(foo(), boo())

Error generated:

Generic struct 'CombineLatest' requires the types 'Error' and 'Never' be equivalent

How to use CombineLates when publisher's failure types are not equivalent?


Solution

  • Whenever you need to match failure types in Combine, for a Never failure type, like a Just publisher, you'd use setFailureType(to:):

    let p: AnyPublisher<Int, Never> = ...
    
    let p1 = p.setFailureType(to: Error.self)
              .eraseToAnyPublisher()  // AnyPublisher<Int, Error>
    

    For a non-Never failure, you'd need to use .mapError:

    let p2 = p1.mapError { CustomError(wrapping: $0) }
               .eraseToAnyPublisher() // AnyPublisher<Int, CustomError> 
    

    So, in your case, if you want to change foo's return value, you'd do:

    func foo() -> AnyPublisher<Int, Error> {
        Result<Int, Never>
            .success(1).publisher
            .setFailureType(to: Error.self)
            .eraseToAnyPublisher()
    }
    

    And if you don't want to change foo, but still use with with bar, you can do:

    Publishers.CombineLatest(foo().setFailureType(to: Error.self), boo())
       .map { (f, b) in
         // f and b are Ints, as emitted by foo and bar, respectively
       }