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?
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.
Most helpful comment
Based on answer the solution is:
And it used like this
But I personally prefer:
This is used like this: