Hi, I have these errors and a flow with flatMap and drive
enum MyError: Error {
case firstFlatMapLatest
case innerFirstFlatMapLatest
case secondFlatMapLatest
case thirdFlatMap
}
button.rx.tap
.flatMapLatest({
return Observable.error(MyError.firstFlatMapLatest)
.flatMapLatest({
return Observable.error(MyError.innerFirstFlatMapLatest)
})
.catchErrorJustReturn(())
})
.flatMapLatest({
return Observable.error(MyError.secondFlatMapLatest).catchErrorJustReturn(())
})
.flatMap({
return Observable.error(MyError.thirdFlatMap).catchErrorJustReturn(())
})
.catchError({ error in
print(error)
return Observable.just(())
})
.map({
return "hello"
})
.asDriver(onErrorRecover: { error in
print(error)
return Driver.just("hi")
})
.drive(onNext: { value in
print(value)
})
Here I must use catchErrorJustReturn for the flow to not be terminated, but what if I don't do catchErrorJustReturn?
flatMapLatest stop the whole flow? In this case catchError is only called onceflatMapLatest?flatMapLatest inside the first flatMapLatest, why don't I need to catchErrorJustReturn for that? like.flatMapLatest({
return Observable.error(MyError.innerFirstFlatMapLatest).catchErrorJustReturn(())
})
flatMap and flatMapLatest hereThis may related to https://github.com/ReactiveX/RxSwift/issues/618, https://github.com/ReactiveX/RxSwift/issues/729, https://github.com/ReactiveX/RxSwift/issues/304
Hi @onmyway133 ,
The behavior you are describing is a consequence of sequence grammar.
https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md#getting-started
Safer approach would be to use Driver in this chain.
button.rx.tap.asDriver()
.flatMapLatest
because compiler will create compile time warnings then.
@kzaher Hi, thanks for your reply. I think we only have a method called asDriver(onErrorJustReturn)?
And why don't we need to catch error in the inner flatMapLatest of the first flatMapLatest?
Hi, thanks for your reply. I think we only have a method called asDriver(onErrorJustReturn)?
Because ControlProperty/ControlEvent/Variable can't produce error, they also have asDriver() method.
And why don't we need to catch error in the inner flatMapLatest of the first flatMapLatest?
The behavior you are describing is a consequence of sequence grammar.
https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md#getting-started
return Observable.error(MyError.firstFlatMapLatest)
.flatMapLatest({
return Observable.error(MyError.innerFirstFlatMapLatest)
})
.catchErrorJustReturn(()) <--- returns a sequence that doesn't fail
@kzaher Hi, sorry if I ask too much 馃槆
In my example, I have .asDriver(onErrorRecover:, do I need to do it? If I need to do it, it seems that .asDriver(onErrorRecover and catchErrorJustReturn in flatMapLatest are trying to catch errors from 2 different sequences?
Hi @onmyway133 ,
I'm really sorry, this is an issues channel only. I've tried to help you and point you in the right direction, but please read the docs. Everything is documented regarding this, and there is a lot of examples in this repo.
@kzaher I read it many times but still don't get it 馃槩 , they are just very simple examples. @icanzilb maybe you can help me understand in layman 's term? I've read many other tutorials, and they recommend the same (catchErrorJustReturn inside flatMap) without explanation
I think catchError is enough to catch the error, but with flatMap we need to catch the error inside, which I don't really understand. I can just blindly follow and it works, but an explanation would be great 馃槆
Hi, @onmyway133 this and this is why it does not happened. When you catch inner sequence it completes and decrements number of active sequences but since outer sequence is probably UI related e.g button taps or text field values they are only complete on deinit that's why sequence is not stopped
But if you catch outer sequence e.g. by doing
button.rx.tap().flatMap {
return //some observable
}.catchErrorJustReturn(something)
it will catch error of outer sequence and complete it . and if it e.g button this will remove RxTarget from the button thats why it won't emit anything
From my experience I can highly recommend reading implementation in case you confused with operator behaviour. It really helped me a lot to understand how everything works
@sergdort Hi, many thanks for answering. I'm checking it out. Will write a detailed explanation about this once I understand it
The answer from @sergdort is right, but it's a little bit hard to understand. Therefore I make sample codes myself to test it. Basically:
Case_A: when you catchError outside flatMap, you are transforming outer observable(s) to new observable (that raises error) -> _the previous observable(s) will be completed due this error_ 鉂楋笍
Case_B: when you catchError inside flatMap, you are transforming outer observable(s) to another new observable (that raise error but covered ) -> _the previous observable(s) don't know about the error and continue to run!_ 馃槇
Note in examples below, I don't call .addDisposableTo(disposeBag). You can see [TAP] disposed itself (while it should dispose manually only when we call .dispose()).
enum MyError: Error {
case fakeError
}
Case_A
_ = buttonLogin.rx.tap.asObservable().debug("[TAP]")
.flatMap {
return Observable<Int>.error(MyError.fakeError)
}.debug("[FLATMAP]")
.catchErrorJustReturn(0).debug("[JUST]")
.subscribe()
// [TAP] --T-|>
// [FLATMAP] --X->
// [JUST] --0-|>
Case_B
_ = buttonLogin.rx.tap.asObservable().debug("[TAP]")
.flatMap {
return Observable<Int>.error(MyError.fakeError).debug("[ERROR]")
.catchErrorJustReturn(0).debug("[JUST]")
}.debug("[FLATMAP]")
.subscribe()
// [TAP] --T--------T----------T------------T--.....-->
// [ERROR] --X-> --X-> --X-> --X->
// [JUST] --0-|> --0-|> --0-|> --0-|>
// [FLATMAP] --0--------0----------0------------0--.....-->
Another case is covering error by catchError( return Observable). Then new observable will continue to run, while previous observable(s) will be completed.
```
_ = buttonLogin.rx.tap.asObservable().debug("[TAP]")
.flatMap {
return Observable
}.debug("[FLATMAP]")
.catchError { (error) in
return Observable
}.debug("[INTERVAL]")
.subscribe()
// [TAP] --T-|>
// [FLATMAP] --X->
// [INTERVAL] --1---2---3---4---5---6---7---8---9--.....-->
````
Most helpful comment
The answer from @sergdort is right, but it's a little bit hard to understand. Therefore I make sample codes myself to test it. Basically:
Case_A: when you
catchErroroutsideflatMap, you are transforming outer observable(s) to new observable (that raises error) -> _the previous observable(s) will be completed due this error_ 鉂楋笍Case_B: when you
catchErrorinsideflatMap, you are transforming outer observable(s) to another new observable (that raise error but covered ) -> _the previous observable(s) don't know about the error and continue to run!_ 馃槇Note in examples below, I don't call
.addDisposableTo(disposeBag). You can see[TAP]disposed itself (while it should dispose manually only when we call.dispose()).Case_A
Case_B
Another case is covering error by.error(MyError.fakeError).interval(1, scheduler: MainScheduler.instance)
catchError( return Observable). Then new observable will continue to run, while previous observable(s) will be completed.```
_ = buttonLogin.rx.tap.asObservable().debug("[TAP]")
.flatMap {
return Observable
}.debug("[FLATMAP]")
.catchError { (error) in
return Observable
}.debug("[INTERVAL]")
.subscribe()
// [TAP] --T-|>
// [FLATMAP] --X->
// [INTERVAL] --1---2---3---4---5---6---7---8---9--.....-->
````