Search code examples
swiftcombine

PassthroughSubject + flatMap cannot be called more than once?


If I send to PassthroughSubject<Void, Error>, this process will run once, but if I send it more than once, the process in the flatMap will not run. Why is this?

var testTappedSubject = PassthroughSubject<Void, Error>()

testTappedSubject
    .print("testTappedSubject")
    .flatMap({ () -> AnyPublisher<Int, Error> in
        print("called test")
        return Fail(error: LoginError.someError(error: .unknown))
                .eraseToAnyPublisher()
    })            
    .sink { error in
        print("error", error)
    } receiveValue: { value in
        print("pressed", value)
    }
    .store(in: &cancellables)

Solution

  • That's the way Combine (and other Reactive frameworks) work.

    you are setting up a subscription to a publisher, and the only thing that subscription does is emit an error:

    return Fail(error: LoginError.someError(error: .unknown))
                    .eraseToAnyPublisher()
    

    When a publisher emits an error. the subscription completes and does not receive any more events.

    I put a version of your code in a playground:

    import Combine
    
    var cancellables = Set<AnyCancellable>()
    
    var testTappedSubject = PassthroughSubject<Void, Error>()
    
    enum LoginError: Error {
        case unknown
    }
    
    testTappedSubject
        .print("testTappedSubject")
        .flatMap({ () -> AnyPublisher<Int, Error> in
            print("called test")
            return Fail(error: LoginError.unknown)
                .eraseToAnyPublisher()
        })
    
        .sink { error in
            print("error", error)
        } receiveValue: { value in
            print("pressed", value)
        }
        .store(in: &cancellables)
    
    testTappedSubject.send()
    testTappedSubject.send()
    testTappedSubject.send()
    

    and the results in the console were:

    testTappedSubject: receive subscription: (PassthroughSubject)
    testTappedSubject: request unlimited
    testTappedSubject: receive value: (())
    called test
    error failure(__lldb_expr_72.LoginError.unknown)
    testTappedSubject: receive value: (())
    testTappedSubject: receive value: (())
    

    This shows that an unlimited subscription was received, and then, after sending a value, your print("called test") is called and then an error is received.

    The subscription is now complete.

    Sending more values just shows that a value was received, but nothing was sent to the subscriber.