iOS RxSwift如何防止序列被处置(抛出错误)?

时间:2018-09-20 13:03:23

标签: ios error-handling observable rx-swift onerror

我有一个由多个运算符组成的序列。在此序列处理过程中,总共有7个地方可能会产生错误。我遇到了一个问题,即该序列的行为不符合我的预期,我正在寻找一种解决该问题的优雅方法:

let inputRelay = PublishRelay<Int>()
let outputRelay = PublishRelay<Result<Int>>()

inputRelay
.map{ /*may throw multiple errors*/}
.flatmap{ /*may throw error*/ }
.map{}
.filter{}
.map{ _ -> Result<Int> in ...}
.catchError{}
.bind(to: outputRelay)

我认为catchError只会捕获错误,让我将其转换为失败结果,但可以防止释放该序列。但是,我看到第一次发现错误时,将重新分配整个序列,并且不再发生任何事件。

没有这种行为,到处都是笨拙的Results <>,并且不得不多次分支序列以将Result.failure(Error)定向到输出。存在不可恢复的错误,因此retry(n)不是一个选择:

let firstOp = inputRelay
.map{ /*may throw multiple errors*/}
.share()

//--Handle first error results--
firstOp
.filter{/*errorResults only*/}
.bind(to: outputRelay)

let secondOp = firstOp
.flatmap{ /*may throw error*/ }
.share()

//--Handle second error results--
secondOp
.filter{/*errorResults only*/}
.bind(to: outputRelay)

secondOp
.map{}
.filter{}
.map{ _ -> Result<Int> in ...}
.catchError{}
.bind(to: outputRelay)

^这非常糟糕,因为大约有7个地方会引发错误,而我不能每次都保持分支的连续性。

RxSwift运算符如何捕获所有错误并在最后发出失败结果,但不将整个序列置于第一个错误上?

2 个答案:

答案 0 :(得分:3)

想到的第一个技巧是使用materialize。这会将每个Observable<T>转换为Observable<Event<T>>,因此错误将只是.next(.error(Error)),不会导致序列终止。

但是,在这种特定情况下,还需要另一个技巧。同样,将您的整个“触发”链放入flatMap中,并materialize放置该特定片段。之所以需要这样做是因为物化序列仍然可以完成,这在规则链的情况下会导致终止,但不会终止flatMapped链(因为flatMap内的complete ==成功完成)。

inputRelay
    .flatMapLatest { val in
        return Observable.just(val)
            .map { value -> Int in
                if value == 1 { throw SomeError.randomError }
                return value + value
            }
            .flatMap { value in
                return Observable<String>.just("hey\(value)")
            }
            .materialize()
    }
    .debug("k")
    .subscribe()

    inputRelay.accept(1)
    inputRelay.accept(2)
    inputRelay.accept(3)
    inputRelay.accept(4)

这将为k输出以下内容:

k -> subscribed
k -> Event next(error(randomError))
k -> Event next(next(hey4))
k -> Event next(completed)
k -> Event next(next(hey6))
k -> Event next(completed)
k -> Event next(next(hey8))
k -> Event next(completed)

现在您要做的就是仅过滤实例化序列中的“ next”事件。

如果您有RxSwiftExt,则只需使用errors()elements()运算符:

stream.elements()
    .debug("elements")
    .subscribe()

stream.errors()
    .debug("errors")
    .subscribe()

这将提供以下输出:

errors -> Event next(randomError)
elements -> Event next(hey4)
elements -> Event next(hey6)
elements -> Event next(hey8)

使用此策略时,请不要忘记在share()之后添加flatMap,因此许多订阅不会引起多处处理。

您可以在此处http://adamborek.com/how-to-handle-errors-in-rxswift/

了解更多有关为什么在这种情况下应该使用共享的信息。

希望这会有所帮助!

答案 1 :(得分:1)

是的,这很痛苦。我考虑过创建一个新的库的想法,该库不需要语法以错误结束流,但是尝试重现整个Rx生态系统似乎毫无意义。

有些反应式库允许您将Never指定为错误类型(这意味着根本不会发出错误),在RxCocoa中,您可以使用Driver(不会出错),但是您可以仍然留下整个结果舞蹈。 “我的Monad中的Monad!”。

要正确处理,您需要一组Monad transformers。有了这些,您就可以完成所需的所有映射/平面映射,而不必担心直到最后都将看到错误。