Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can an Rx Observable gracefully handle exceptions in an operator and continue?

i.e., by passing the error condition and not halting the entire Observable?

My Observable starts with a user-supplied list of package tracking numbers from common delivery services (FedEx, UPS, DHL, etc), looks up the expected delivery date online, then returns those dates in terms of number of days from today (i.e. "in 3 days" rather than "Jan 22"). The problem is that if any individual lookup results in an exception, the entire stream halts, and the rest of the codes won't be looked up. There's no ability to gracefully handle, say, UnknownTrackingCode Exception, and so the Observable can't guarantee that it will look up all the codes the user submitted.

public void getDaysTillDelivery(List<String> tracking_code_list) {
        Observable o = Observable.from(tracking_code_list)
                   // LookupDeliveryDate performs network calls to UPS, FedEx, USPS web sites or APIs
                   // it might throw: UnknownTrackingCode Exception, NoResponse Exception, LostPackage Exception
                .map(tracking_code -> LookupDeliveryDate(tracking_code))
                .map(delivery_date -> CalculateDaysFromToday(delivery_date));
        o.subscribe(mySubscriber); // will handle onNext, onError, onComplete
}

Halting the Observable stream as a result of one error is by design:

  • http://reactivex.io/documentation/operators/catch.html
  • Handling Exceptions in Reactive Extensions without stopping sequence
  • https://groups.google.com/forum/#!topic/rxjava/trm2n6S4FSc

The default behavior can be overcome, but only by eliminating many of the benefits of Rx in the first place:

  • I can wrap LookupDeliveryDate so it returns Dates in place of Exceptions (such as 1899-12-31 for UnknownTrackingCode Exception) but this prevents "loosely coupled code", because CalculateDaysFromToday would need to handle these special cases
  • I can surround each anonymous function with try/catch and blocks, but this essentially prevents me from using lambdas
  • I can use if/thens to direct the code path, but this will likely require maintaining some state and eliminating deterministic evaluation
  • Error handling of each step, obviously, prevents consolidating all error handling in the Subscriber
  • Writing my own error-handling operator is possible, but thinly documented

Is there a better way to handle this?

like image 611
ExactaBox Avatar asked Jan 19 '15 19:01

ExactaBox


People also ask

How to handle exception in RxJava?

Even though RxJava has its own system for handling errors, checked exceptions still must be handled by your code. That means you have to add your own try-catch block. You don't need to call onError in the case of a checked exception, either. You could always handle your checked exception but keep the sequence going.

Which operator is used to forward a failed observable to the error handler?

The catchError operator is going to take the error and pass it to the error handling function. That function is expected to return an Observable which is going to be a replacement Observable for the stream that just errored out.


1 Answers

What exactly do you want to happen if there is an error? Do you just want to throw that entry away or do you want something downstream to do something with it?

If you want something downstream to take some action, then you are really turning the error into data (sort of like your example of returning a sentinel value of 1899-12-31 to represent the error). This strategy by definition means that everything downstream needs to understand that the data stream may contain errors instead of data and they must be modified to deal with it.

But rather than yielding a magic value, you can turn your Observable stream into a stream of Either values. Either the value is a date, or it is an error. Everything downstream receives this Either object and can ask it if it has a value or an error. If it has a value, they can produce a new Either object with the result of their calculation. If it has an error and they cannot do anything with it, they can yield an error Either themselves.

I don't know Java syntax, but this is what it might look like in c#:

Observable.From(tracking_code_list)
    .Select(t =>
        {
            try { return Either.From(LookupDeliveryDate(t)); }
            catch (Exception e)
            {
                return Either.FromError<Date>(e);
            }
        })
     .Select(dateEither =>
        {
            return dateEither.HasValue ?
                Either.From(CalculateDaysFromToday(dateEither.Value)) :
                Either.FromError<int>(dateEither.Error);
        })
     .Subscribe(value =>
        {
            if (value.HasValue) mySubscriber.OnValue(value.Value);
            else mySubscribe.OnError(value.Error);
        });

Your other option is the "handle"/suppress the error when it occurs. This may be sufficient depending on your needs. In this case, just have LookupDeliveryDate return magic dates instead of exceptions and then add a .filter to filter out the magic dates before they get to CalculateDaysFromToay.

like image 110
Brandon Avatar answered Oct 06 '22 03:10

Brandon