简体   繁体   English

Swift Combine 接收器在第一次错误后停止接收值

[英]Swift Combine sink stops receiving values after first error

Im moving my project to Combine from RxSwift I have a logic where I want publisher to emit event every time I click button.我将我的项目从 RxSwift 移动到 Combine 我有一个逻辑,我希望发布者每次单击按钮时都发出事件。 Acrually clicking button executed pushMe.send()实际点击按钮执行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.其余的点击将被忽略。

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.因此,如果您的 Future 产生错误,它会作为错误传递给 Sink,因此管道一直被取消直到发布者。

The pattern for preventing this is to deal with the error inside the FlatMap .防止这种情况的模式是处理FlatMap 内部的错误 Basically, you've got two pipelines here: the one that starts with pushMe and the one that starts with Future.基本上,这里有两条管道:一条以pushMe ,一条以 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.简单地不要让 Future 管道产生的错误“泄漏”到pushMe管道中,这样pushMe管道就不会被取消。 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.相反,捕获 FlatMap 中的错误,如果您想将某些内容传递给您的 Sink,请从中传递某种,告诉您的 Sink 存在错误的输入。

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.在您的情况下,一个简单的解决方案是将您的 FlatMap 类型更改为<Bool,Never> ,并将truefalse作为 Bool 传递以指示验证是否在 Future 中成功。

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.或者,如果将有关错误的更多详细信息传递到管道中对您很重要,请将 FlatMap 的类型更改为<Result<Bool,Error>,Never>并将错误信息打包到 Result 对象的.failure案例中.

What does flatMap do - flatMap 做什么 -

  1. Subscribes to the given publisher (let's say XPublisher).订阅给定的发布者(比方说 XPublisher)。
  2. Sends the Errors and Output values (not finished event/ completion) emitted by XPublisher to the down stream.将 XPublisher 发出的错误和输出值(未完成的事件/完成)发送到下游。

So If you handle errors inside the flat map , (which means the publisher inside the flatMap does not emit errors), then flatMap Never sends an error to the down stream.因此,如果您在 flatMap 内部处理错误(这意味着 flatMap 内部的发布者不会发出错误),则 flatMap 从不向下游发送错误。

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

Otherwise you can handle error outside the fatMap.否则,您可以在 fatMap 之外处理错误。 Problem here is that, once an error out whole the subscription / cancellable cancelled.这里的问题是,一旦出现错误,订阅/可取消就会被取消。 ( in the below example error has replace with a false value) (在下面的示例中,错误已替换为false值)

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

What is happening in the above code.上面代码中发生了什么。

  1. FlatMap error outs. FlatMap error输出。
  2. replace the error with false (One false value will receive Because of this)用 false 替换错误(因此会收到一个false值)
  3. subscription cancelled because of the error out in the stream.由于流中的错误而取消订阅。

This is how Publisher s work in Combine.这就是Publisher在 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. Publisher可以发出值或发出完成事件——一旦发出完成事件, Publisher就完成了,它不能发出任何其他值或另一个完成事件。 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.如果Publisher发出错误,则错误将作为failure完成发出,这意味着一旦发出错误, Publisher完成并且不能再发出任何值。

There are several Combine operators designed for handling errors without completing the Publisher .有几个 Combine 运算符设计用于在不完成Publisher情况下处理错误。 Have a look into the catch operator for instance.例如,查看catch运算符。

First, thanks all for helping with this question.首先,感谢大家帮助解决这个问题。

Answer of @matt is one of the possible solution. @matt 的答案是可能的解决方案之一。 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()好处是你可以在一个地方处理所有类型的错误,如果第二个 sink()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM