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

Ale*_*one 2 error-handling onerror observable ios rx-swift

我有一个由多个运算符组成的序列。在此序列处理过程中,总共有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)
Run Code Online (Sandbox Code Playgroud)

我以为那只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)
Run Code Online (Sandbox Code Playgroud)

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

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

Sha*_*ali 6

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

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

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)
Run Code Online (Sandbox Code Playgroud)

这将输出以下内容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)
Run Code Online (Sandbox Code Playgroud)

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

如果您具有RxSwiftExt,则可以简单地使用errors()elements()运算符:

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

stream.errors()
    .debug("errors")
    .subscribe()
Run Code Online (Sandbox Code Playgroud)

这将提供以下输出:

errors -> Event next(randomError)
elements -> Event next(hey4)
elements -> Event next(hey6)
elements -> Event next(hey8)
Run Code Online (Sandbox Code Playgroud)

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

您可以在此处阅读有关为何在这种情况下应该使用共享的更多信息:http : //adamborek.com/how-to-handle-errors-in-rxswift/

希望这可以帮助!

  • 我注意到,掌握过滤掉 flatMap 本身和主序列链中的错误之间的差异可能很棘手。为了使知识系统化,我在游乐场项目中测试了一些东西并将其写下来;也许有人会发现它作为您答案的补充很有用;)https://link.medium.com/43OYARwDaZ (2认同)

Dan*_* T. 3

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

有一些反应式库允许您指定Never错误类型(意味着根本无法发出错误),并且在 RxCocoa 中您可以使用 Driver(不会出错),但您仍然需要完成整个 Result 舞蹈。“我的单子中的单子!”。

为了正确处理它,你需要一组Monad 转换器。有了这些,您可以完成您想要的所有映射/平面映射,并且直到最后才担心查看错误。