Swift Combine sink stops receiving values after first error

泪湿孤枕 提交于 2021-02-04 07:41:04

问题


Im moving my project to Combine from RxSwift I have a logic where I want publisher to emit event every time I click button. Acrually clicking button executed pushMe.send()

pushMe
            .print("Debug")
            .flatMap { (res) -> AnyPublisher<Bool, Error> in
                return Future<Bool, Error>.init { closure in
                    closure(.failure(Errors.validationFail))
                }.eraseToAnyPublisher()
            }
            .sink(receiveCompletion: { completion in
                print("Completion received")
            }, receiveValue: { value in
                print("Value = \(value)")
            })
            .store(in: &subscriptions)

The console result

Debug: receive value: (true)
Completion received
Debug: receive value: (true)
Debug: receive value: (true)

I do not understand why sink receive error only on first event. The rest clicks are ignored.


回答1:


The rule is that if an error propagates down the pipeline, the entire pipeline is cancelled. Thus, if your Future generates an error, it passes as an error to the Sink and thus the pipeline is cancelled all the way up to the Publisher.

The pattern for preventing this is to deal with the error inside the FlatMap. Basically, you've got two pipelines here: the one that starts with pushMe and the one that starts with Future. Simply don't let the error generated by the Future pipeline "leak" out into the pushMe pipeline, and so the pushMe pipeline will not be cancelled. Instead, catch the error inside the FlatMap and, if you want to pass something out of it to your Sink, pass out of it some sort of value that tells your Sink that there has been a bad input.

A simple solution in your case would be to change the type your FlatMap to <Bool,Never>, and pass either true or false as the Bool to indicate whether validation succeeded in the Future or not.

Or, if it's important to you to pass more detailed information about the error down the pipeline, change the type of your FlatMap to <Result<Bool,Error>,Never> and package the error information into the .failure case of the Result object.




回答2:


This is how Publishers work in Combine.

The Publisher can either emit values or emit a completion event - once a completion event was emitted, the Publisher is finished and it cannot emit any other values or another completion event. If the Publisher emits an error, the error is emitted as a failure completion, meaning that once an error is emitted, the Publisher completes and it cannot emit any more values.

There are several Combine operators designed for handling errors without completing the Publisher. Have a look into the catch operator for instance.




回答3:


First, thanks all for helping with this question.

Answer of @matt is one of the possible solution. Another solution is to create new pipeline every time you clicking button. Im using this approach because I have sequence of steps below failing publisher and Im not able to rely of dummy true/false result further.

      Just<String>()
            .sink(receiveValue: { value in
                startProcess()
                        .sink(receiveCompletion: { (completion:
                                Subscribers.Completion<Failure>) in
                            // can handle ALL types of error of pipe line in ONE place
                        }, receiveValue: { (v: P.Output) in
                            // handle correct result           
                        })
            })
            .store(in: &subscriptions)

    func startProcess() -> AnyPublisher<Bool, Error> {
        Future<Bool, Error>.init { closure in
                    // action 1 
                    closure(.success(true))
                }
                .flatMap { (b: Bool) -> AnyPubilsher<Void, Error> in
                    Future<Bool, Error>.init { closure in
                        // action 2
                        closure(.success(()))
                    }
                }
    }

Benefit is that you are able to handle all types of errors in one place if second sink()



来源:https://stackoverflow.com/questions/64444966/swift-combine-sink-stops-receiving-values-after-first-error

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!