iOS RxSwift如何防止序列被丢弃(抛出错误)? [英] iOS RxSwift how to prevent sequence from being disposed on (throw error)?

查看:185
本文介绍了iOS RxSwift如何防止序列被丢弃(抛出错误)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个由多个运算符组成的序列.在此序列处理过程中,总共有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运算符如何捕获所有错误并在最后发出失败结果,但不将整个序列置于第一个错误处?

解决方案

想到的第一个技巧是使用materialize.这样会将每个Observable<T>转换为Observable<Event<T>>,因此Error只是一个.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)

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

您可以在此处详细了解为什么要在这种情况下使用共享: http://adamborek.com/how-to-handle-errors-in-rxswift/

希望这会有所帮助!

I have a sequence made up of multiple operators. There are total of 7 places where errors can be generated during this sequence processing. I'm running into an issue where the sequence does not behave as I expected and I'm looking for an elegant solution around the problem:

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)

I thought that catchError would simply catch the error, allow me to convert it to failure result, but prevent the sequence from being deallocated. However, I see that the first time an error is caught, the entire sequence is deallocated and no more events go through.

Without this behavior, I'm left with a fugly Results<> all over the place, and have to branch my sequence multiple times to direct the Result.failure(Error) to the output. There are non-recoverable errors, so retry(n) is not an option:

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)

^ Which is very bad, because there are around 7 places where errors can be thrown and I cannot just keep branching the sequence each time.

How can RxSwift operators catch all errors and emit a failure result at the end, but NOT dispose the entire sequence on first error?

解决方案

The first trick to come to mind is using materialize. This would convert every Observable<T> to Observable<Event<T>>, so an Error would just be a .next(.error(Error)) and won't cause the termination of the sequence.

in this specific case, though, another trick would be needed. Putting your entire "trigger" chain within a flatMap, as well, and materializeing that specific piece. This is needed because a materialized sequence can still complete, which would cause a termination in case of a regular chain, but would not terminate a flatMapped chain (as complete == successfully done, inside a 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)

This will output the following for 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)

Now all you have to do is filter just the "next" events from the materialized sequence.

If you have RxSwiftExt, you can simply use the errors() and elements() operators:

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

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

This will provide the following output:

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

When using this strategy, don't forget adding share() after your flatMap, so many subscriptions don't cause multiple pieces of processing.

You can read more about why you should use share in this situation here: http://adamborek.com/how-to-handle-errors-in-rxswift/

Hope this helps!

这篇关于iOS RxSwift如何防止序列被丢弃(抛出错误)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆