【问题标题】:How to continue subscribing to publisher after error?出错后如何继续订阅发布者?
【发布时间】:2020-05-29 16:13:58
【问题描述】:

我正在尝试建立一个发布者来发布一组整数,并且在某些时候可能会失败。它有点做作,但希望能说明原理。下面的例子。

enum NumberError: Int, Error {
   case isFatal, canContinue
}

struct Numbers {
    let p = PassthroughSubject<Int, NumberError>()

    func start(max: Int) {

        let errorI = Int.random(in: 1...max)
        for i in (1...max) {
            if errorI == i {
                p.send(completion: .failure(NumberError.canContinue))
            } else {
                p.send(i)
            }
        }
        p.send(completion: .finished)

    }
}

然后我使用以下方式订阅:

let n = Numbers()
let c = n.p
    .catch {_ in return Just(-1)}

    .sink(receiveCompletion: {result in
        switch result {
        case .failure:
            print("Error")
        case .finished:
            print("Finished")
        }
    }, receiveValue: {
        print($0)
    })

n.start(max: 5)

这可以用 -1 替换错误,但我想继续接收值。有谁知道这是否可能? 阅读并环顾四周,似乎 flatMap 可能是要走的路,但我无法确定在闭包中使用哪个发布者?非常感谢任何帮助。

【问题讨论】:

    标签: swift combine


    【解决方案1】:

    我认为您错误地认为 PassthroughSubject 在发布失败后可以发布更多输出。这不可以。在您调用p.send(completion: ...) 后,任何对p.send(...) 的调用都将被忽略。此外,如果您在调用p.send(completion: ...) 后订阅pp 将立即完成新订阅并且不会发送任何输出。

    因此,如果您想在之后发送更多值,则不能将错误发送为 .failure。相反,将发布者的 Output 类型更改为 Result&lt;Int, NumberError&gt; 并将其 Failure 类型更改为 Never

    import Combine
    
    enum NumberError: Int, Error {
       case isFatal, canContinue
    }
    
    struct Numbers {
        let p = PassthroughSubject<Result<Int, NumberError>, Never>()
    
        func start(max: Int) {
            let bad = (max + 1) / 2
            for i in (1...max) {
                if bad == i {
                    p.send(.failure(NumberError.canContinue))
                } else {
                    p.send(.success(i))
                }
            }
            p.send(completion: .finished)
        }
    }
    

    但是现在您不能使用catch 来处理错误,因为它不会以失败的形式出现。相反,您可以使用map:

    let n = Numbers()
    let c = n.p
        .map({
            switch $0 {
            case .success(let i): return i
            case .failure(_): return -1
            }
        })
        .sink(receiveCompletion: {result in
            switch result {
            case .failure:
                print("Error")
            case .finished:
                print("Finished")
            }
        }, receiveValue: {
            print($0)
        })
    
    n.start(max: 5)
    

    输出:

    1
    2
    -1
    4
    5
    Finished
    

    【讨论】:

    • 非常感谢您的解释和替代方法。您是否知道 WWDC 2019 Combine 中的示例在实践中是如何恢复的?我相信他们使用 flatMap 从编码/解码错误中恢复。
    • 在该会话中,根发布者是NotificationCenter.Publisher,它永远不会失败。该 WWDC 示例中的故障发生在 flatMap 内,并使用 flatMap 内的 .catch 恢复。失败并没有逃脱flatMap。在您的问题中,您的根发布者是 PassthroughSubject,您通过它发送失败消息。
    猜你喜欢
    • 2017-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-28
    相关资源
    最近更新 更多