【问题标题】:iOS RxSwift how to prevent sequence from being disposed on (throw error)?iOS RxSwift 如何防止序列被处理(抛出错误)?
【发布时间】:2019-02-24 19:20:39
【问题描述】:

我有一个由多个运算符组成的序列。在这个序列处理过程中,总共有 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 会简单地捕获错误,允许我将其转换为失败结果,但防止序列被释放。但是,我看到第一次发现错误时,整个序列都被释放,不再有事件通过。

如果没有这种行为,我会留下一个令人毛骨悚然的结果,并且必须多次分支我的序列以将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 运算符如何捕获所有错误并在最后发出失败结果,但不处理第一个错误时的整个序列?

【问题讨论】:

  • 恐怕这是 Rx 的方式,它不是关于资源被释放的问题,如果观察者发出错误,它被标记为已完成,正如 Rx 所说,一个 observable 不能在之后发出值发生错误。所以你这里只有一种方法,如果可以,不要发出错误,你可以将游览值和错误包装到一个将一直发出的对象中,订阅者必须检查是否有错误。您也可以尝试使用重试,它使您可以选择返回一个观察者,如果发生错误(不仅在 n 次之后)将执行该观察者
  • 如果在 UI 中使用它,您不能使用驱动程序并在它捕获该运算符的任何错误时返回一些东西吗? .asDriver(onErrorJustReturn: nil) 因为驱动程序永远不会出错。

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


【解决方案1】:

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

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

要妥善处理,需要一组Monad transformers。有了这些,你可以做所有你想要的映射/平面映射,而不必担心直到最后才看到错误。

【讨论】:

【解决方案2】:

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

但是,在这种特定情况下,需要另一个技巧。将你的整个“触发”链也放在一个 flatMap 中,并 materializeing 那个特定的部分。这是必需的,因为物化序列仍然可以完成,这会在常规链的情况下导致终止,但不会终止 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)

这将为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)

现在您所要做的就是从具体化序列中过滤“下一个”事件。

如果您有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)

在使用这个策略的时候,别忘了在你的flatMap后面加上share(),这么多订阅不会造成多片处理。

你可以在这里阅读更多关于为什么你应该在这种情况下使用共享:http://adamborek.com/how-to-handle-errors-in-rxswift/

希望这会有所帮助!

【讨论】:

  • 我注意到,在 flatMap 本身和主序列链中过滤掉错误之间的区别可能很棘手。为了将知识系统化,我在操场项目中测试了一些东西并将其写下来;也许有人会发现它可以作为您答案的补充;)link.medium.com/43OYARwDaZ
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-17
  • 2015-12-01
  • 2018-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多