RuntimeExceptions are supposed to indicate programming error and I want my application to crash when something inside my observables throws RuntimeException.
What is the best way to do this? Right now I'm considering this solution (it's Kotlin, but I hope it's understandable)
fun <T> Observable<T>.subscribeCrashOnRuntimeException(onNext: (T) -> Unit, onError: (Throwable) -> Unit) {
this.subscribe({
onNext(it)
}, { e ->
if (e is RuntimeException) {
throw e
} else {
onError(e)
}
})
}
fun usageExample() {
val observable = Observable.just(1)
observable.subscribeCrashOnRuntimeExceptions(
{ next -> Log.d("TAG", "next: $next") },
{ e -> Log.d("TAG", "error: $e") }
)
}
But I have doubts about it. For example it is hard to occasionally "catch" specific RuntimeExceptions with this solution. Perhaps there is a well known way to deal with situation that I just don't know how to google?
I don't think there should be much difference in handling runtime (aka unchecked) or regular (aka checked) exceptions. Both are widely used these days and could be either recoverable or not depending on particular situation.
The reactive ways of handling errors are:
onErrorResumeNext
or onErrorReturn
operators; these allow to inspect the error and possibly recover from itretry*
family of operators; these allow to inspect the error and possibly recover from it by means of re-subscribing (for example retry network call)onError
callback of your subscribers; by the way in case you do not supply such a callback the error will be re-thrown in regular Java fashion, so your program will crashRelated topic: How to handle different kinds of errors in Retrofit Rx onError without ugly instanceof
Also note the drawbacks of throwing exceptions in regular Java way:
Sample code
Observable.fromCallable(() -> {
...
if (ok) return "Success!";
else throw new RuntimeException("Failure at source");
})
.map(s -> {
... processing is bypassed in case of an error
})
.map(s -> {
...
if (...) return s.upperCase();
else throw new RuntimeException("Failure during processing");
})
.onErrorReturn(e -> {
if (e.getMessage().contains("processing"))
return "Recovered";
throw Exceptions.propagate(e); // let it continue as an error
})
.subscribe(s -> println("got result: " + s),
e -> println("got error: " + e);
All exceptions get caught by RxJava and passed along the defined route.
onError*
operators act like intermediate catch
blocks.
Subscriber's onError
callback acts like top-level catch
block.
More links on the subject: