0
votes

I have an observable (request from network) and dont want it to be disposed when I got an error

My custom error

   enum MyError: Error {
        case notFound
        case unknown
    }

My network request using Moya

let registerRequest = didTapJoinButton.withLatestFrom(text.asObservable())
            .flatMapLatest { text in
                provider.rx.request(API.register(text: text))
            }
            .flatMapLatest({ (response) -> Observable<Response> in
                let statusCode = response.statusCode

                if statusCode.isSuccessStatus() {
                    return Observable.just(response)

                } else if statusCode.isNotFoundStatus() {
                    return Observable.error(MyError.notFound)

                } else {
                    return Observable.error(MyError.unknown)
                }
            })
            .materialize()
            .share(replay: 1)

Looks great. I use materialize() to prevent observable being disposed on error

Subscribe: (If status code 200) Everything works just fine, I got response and the stream is not disposed

   registerEventRequest.subscribe(onNext: { (next) in
            print("NEXT: \(next)")
        }, onError: { (error) in
            print("ERRRRROR ME: \(error)")
        }, onCompleted: {
            print("Completed")
        }) {
            print("Disposed")
        }

BUT if status code is something like 404. I got the error as I expected. However, hey look at the console log

NEXT: error(notFound)
Completed
Disposed

It jumps to NEXT which I expected. But why it throw complete and dispose my sequence.

My question is Why did it dispose my sequence and how I can prevent this?

1

1 Answers

2
votes

.materialize() does not prevent an observable from being disposed on error. When an Observable emits an error, it is finished and materialize merely converts that error into a next event.

You need to put the materialize inside the first flatMapLatest to prevent the error from escaping the flatMap closure.

This video might help (note selectMany is the same as flatMap) https://channel9.msdn.com/Blogs/J.Van.Gogh/Reactive-Extensions-API-in-depth-SelectMany?term=select%20many&lang-en=true


Here's another way to compose the Observable:

let registerRequest = didTapJoinButton.withLatestFrom(text.asObservable())
    .flatMapLatest { text in
        provider.rx.request(API.register(text: text))
            .materialize()
    }
    .map { (event) -> Event<Response> in
        switch event {
        case .next(let response) where response.statusCode.isNotFoundStatus():
            return Event.error(MyError.notFound)

        case .next(let response) where response.statusCode.isSuccessStatus() == false:
            return Event.error(MyError.unknown)

        default:
            return event
        }
    }
    .share(replay: 1)

I moved materialize() where it belongs so errors won't break the chain. I also swapped out the second flatMapLatest for a simple map since the extra work wasn't necessary.

The switch statement could have also been written like this:

switch event {
case .next(let response):
    let statusCode = response.statusCode
    if statusCode.isNotFoundStatus() {
        return Event.error(MyError.notFound)
    }
    else if statusCode.isSuccessStatus() {
        return event
    }
    else {
        return Event.error(MyError.unknown)
    }

default:
    return event
}

But I think the way I did it is cleaner because it reduces the cyclomatic complexity of the closure.


Here is code to deal with the concern brought up in the comments:

extension ObservableType {
    func flatMapLatestT<T, U>(_ selector: @escaping (T) -> Observable<U>) -> Observable<Event<U>>
        where Self.E == Event<T>
    {
        return self.flatMapLatest { (event) -> Observable<Event<U>> in
            switch event {
            case .next(let element):
                return selector(element).materialize()
            case .completed:
                return .just(Event<U>.completed)
            case .error(let error):
                return .just(Event<U>.error(error))
            }
        }
    }
}

This gist contains a whole suite of operators for dealing with Events. https://gist.github.com/dtartaglia/d7b8d5c63cf0c91b85b629a419b98d7e