8

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:

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?

Community
  • 1
  • 1
ExactaBox
  • 3,235
  • 16
  • 27

1 Answers1

4

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.

Brandon
  • 38,310
  • 8
  • 82
  • 87
  • _What exactly do you want to happen if there is an error?_ Ideally, something like `onItemError()` in the Subscriber which can be written to cover the possible known Exception types on an item-by-item basis, while the Observer continues processing the stream. Since posting, I've read more, and this can sort-of be simulated by `flatMap`'ing each tracking code string to a single-item `Observable`, so now `onError` or `onComplete` gets called for every item. Hack-ish, but might work. – ExactaBox Jan 19 '15 at 21:40
  • I appreciate your suggestions and want to address them: Modify downstream actions -- could work, but it violates "loosely coupled" concept and action functions can't always be changed. Filter out errors -- maybe in certain workflows, but unacceptable when a response is expected for every input. `Either` object -- not native to Java 7 (Android), and you lose single-line lambdas, but conceptually it seems less hack-ish and more lightweight than converting a stream of `String` into a stream of `Observable`, each of which requires a unique subscription. Thank you! – ExactaBox Jan 19 '15 at 21:47
  • Yes Either does not exist in c# either. You'd need to roll your own version of that class concept. – Brandon Jan 19 '15 at 23:08