Rxswift: `catchError` behavior only tied to latest observable sequence???

Created on 4 Dec 2015  路  21Comments  路  Source: ReactiveX/RxSwift

Hey guys,

I have problems in understanding error catching and continuing the observable sequence in case of an error.

I now have some sequences like this one:

function doManyThings() -> Observable<AnyType> {
    return crazyFunction
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
}

button.rx_tap
    .flatMap(doManyThings)
    .catchError { handleErrors($0) }
    .subscribeNext { input in
        // do something
    }

Is it not possible to catch all the errors in a central place without doing subscribeError which cancels the subscription entirely??? I now have the case that .catchError() is called, no matter what error, but when I tap button a second time, nothing happens because it seems the subscription is canceled.

How do you solve this kind of issue?

Most helpful comment

Based on answer the solution is:

    @warn_unused_result(message="http://git.io/rxs.uo")
    public func flatMapLatest<O: ObservableConvertibleType>(selector: (Self.E) throws -> O,
                              onError: (ErrorType throws -> Void)) -> RxSwift.Observable<O.E> {

        return flatMapLatest({ x in
            try selector(x).asObservable().catchError { error in
                try onError(error)
                return Observable.never()
            }
        })
    }

And it used like this

button.rx_tap
    .flatMapLatest(doManyThings, onError: { 
          //handle Error
    }).subscribeNext { input in
        // do something
    }

But I personally prefer:

    @warn_unused_result(message="http://git.io/rxs.uo")
    public func catchErrorAndContinue(handler: (ErrorType) throws -> Void) -> RxSwift.Observable<Self.E> {
        return self.catchError { error in
            try handler(error)
            return Observable.error(error)
        }.retry()
    }

This is used like this:

button.rx_tap
    .flatMapLatest { doManyThings }
    .catchErrorAndContinue {
        // handle error
    }.subscribeNext { input in
        // do something
    }

All 21 comments

You can map to a wrapper type which can carry error, e.g. Optional

function doManyThings() -> Observable<Optional<AnyType>> {
    return crazyFunction
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .map(Optional.Some)
        .catchErrorJustReturn(Optional.None)
}

button.rx_tap
    .flatMap(doManyThings)
    .subscribeNext { input in
        // do something
    }

if you need complex wrapper, carry the error information,
you can use a self-defined either type

enum Result<OutputType>{
    case Success(OutputType)
    case Error(ErrorType)
}

Then you just replace '.map' '.catchError' with

        .map(Result.Success($0))
        .catchError{ just(Result.Error($0) }

sorry, that's not an option as the error information gets lost! For me that behavior is a major problem, if it is like I wrote...

if you need complex wrapper, carry the error information,
you can use a self-defined either type

enum Result<OutputType>{
    case Success(OutputType)
    case Error(ErrorType)
}

Then you just replace '.map' '.catchError' with

        .map(Result.Success)
        .catchError{ just(Result.Error($0)) }

so, the other option is to carry through the errorType with a Either or Result type. but then all signatures have to be changed to these types

Result is generic Type, it can carry both your result type and error.

and I have to carry all previous errors through all these functions

Yes, this is obvious, without Generics you would be dead here...

Result is generic only for the Result, when it is an error, all error can be carried by Result, because all error is ErrorType

enum Result<OutputType>{
    case Success(OutputType)
    case Error(ErrorType)
}

yes sure, but if you have ten methods, in every method I have to check for error and then only go on if there is none... this cannot be in the way of Rx

function doManyThings() -> Observable<Result<AnyType>> {
    return crazyFunction
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .map(Result.Success)
        .catchError{ just(Result.Error($0))}
}

button.rx_tap
    .flatMap(doManyThings)
    .subscribeNext { result in
        switch result{
             case .Success(let output):
                    //receive final result
            case .Error(let error):
                  //receive all possible error from all functions above.
       }

        // do something
    }

The good thing about error handling is that when the error occurs, all the following sequence parts immediately get skipped and it jumps directly to the error handler

ok, I give it another shot with moving the catch inside the other function. That should be an option

This code can catch all error above catchError.
and because there is an outer flatMap, so the tap event sequence will not be terminated by error.

You can use https://github.com/antitypical/Result as an either type carry both the output and error
This is my implementation with antitypical/Result

public enum Break: ErrorType{
    case Cancelled
    case Timeout
    case Unknown
    case Error(ErrorType)

    var error: ErrorType? {
        switch self {
        case .Error(let error): return error
        default: return nil
        }
    }

    var nsError:  NSError? {
        guard let error = error else { return nil }
        guard error.dynamicType == NSError.self else { return nil }
        return error as NSError
    }
}
import Result
import RxSwift
extension ObservableType {
    @warn_unused_result(message="http://git.io/rxs.uo")
    public func mapToFailable() -> Observable<Result<E, Break>>{
        return self
            .map(Result<E, Break>.Success)

            // catch error map to Result.Failure(Break.Error(error)), so the signal on this subject will not interrupt its super subject
            .catchError{  just(.Failure(.Error($0))) }
    }
}

Then

function doManyThingsMayFail() -> Observable<AnyType> {
    return crazyFunction
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
}

button.rx_tap
    .flatMap{ _ in doManyThingsMayFail.mapToFailable() }
    .subscribeNext { result in
        switch result{
             case .Success(let output):
                    //receive final result
             case .Failure(let break):
                    if let error = break.error { print(error) }
                    //receive all possible breaks from all functions above.
       }
    }

yes, this is a solution. I will tell you about my success

Hi @Fab1n ,

Maybe I'm missing something, but I believe something like this should work

function doManyThings() -> Observable<AnyType> {
    return crazyFunction
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
        .flatMap(functionThatCanErrorOut)
}

button.rx_tap
    .flatMapLatest { _ in
         return doManyThings()
               .catchError { handleErrors($0) }
    }
    .subscribeNext { input in
        // do something
    }

... or you can create your own ObservableType extension method so you can write

    button.rx_tap
    .flatMapLatest(doManyThings, onError: handleErrors)
    .subscribeNext { input in
        // do something
    } 

extension ObservableType {
   @warn_unused_result(message="http://git.io/rxs.uo")
    public func flatMapLatest<O: ObservableConvertibleType>(selector: (E) throws -> O, onError: (ErrorType) -> Observable<O.E>)
        -> Observable<O.E> {
           return flatMapLatest { x in
                  return selector(x).catchError(onError)
           }
    }
}

... or some other type of custom extensions
... or do something with optionals, Result enum .... return values like @frogcjn suggested.

:)

guys, it works as expected. Catching in an inner flatMap or map does it all!!! That's the trick!

@Fab1n Thanks for your response.

Catching in an inner flatMap or map does it all!!! That's the trick!

I've been trying to figure this out all day and your comment just made everything click into place.

Based on answer the solution is:

    @warn_unused_result(message="http://git.io/rxs.uo")
    public func flatMapLatest<O: ObservableConvertibleType>(selector: (Self.E) throws -> O,
                              onError: (ErrorType throws -> Void)) -> RxSwift.Observable<O.E> {

        return flatMapLatest({ x in
            try selector(x).asObservable().catchError { error in
                try onError(error)
                return Observable.never()
            }
        })
    }

And it used like this

button.rx_tap
    .flatMapLatest(doManyThings, onError: { 
          //handle Error
    }).subscribeNext { input in
        // do something
    }

But I personally prefer:

    @warn_unused_result(message="http://git.io/rxs.uo")
    public func catchErrorAndContinue(handler: (ErrorType) throws -> Void) -> RxSwift.Observable<Self.E> {
        return self.catchError { error in
            try handler(error)
            return Observable.error(error)
        }.retry()
    }

This is used like this:

button.rx_tap
    .flatMapLatest { doManyThings }
    .catchErrorAndContinue {
        // handle error
    }.subscribeNext { input in
        // do something
    }

@frogcjn it means if we have outer flatmap it wont terminate observables sequence? In that case we will have to encapsulate under flatmap for each method? I am stuck there trying to come up with solution.

@Fab1n would appreciate if you can share your sol as well.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

marlowcharite picture marlowcharite  路  3Comments

retsohuang picture retsohuang  路  3Comments

gaudecker picture gaudecker  路  3Comments

hannesstruss picture hannesstruss  路  3Comments

acecilia picture acecilia  路  3Comments