10

Why RX has the following grammar OnNext* (OnError|OnCompleted)? instead of (OnNext|OnError)* OnCompleted? This is quite clear from implementation perspective(also this has common semantics with IEnumerable and yield) but I guess differs from real life situation. In real life - producers generate mixed stream of data and exceptions (and exceptions doesn't break producer).

The question: If I understood correctly the only possible solution is to make observable return complex data structure combined from initial data and produced exceptions(Observable.Timestamp() and .TimeInterval() has similar concept) or are there any other options?


At the moment I came to the following solution: Inside observable producer I manually handle exeptions and just pass them to the following data structure which is the result of my observable

public class ValueOrException<T>
{
    private readonly Exception ex;
    private readonly T value;

    public ValueOrException(T value, Exception ex)
    {
        this.value = value;
        this.ex = ex;
    }

    public ValueOrException(T value)
    {
        this.value = value;
    }

    public T Value
    {
        get { return this.value; }
    }

    public Exception Ex
    {
        get { return this.ex; }
    }
}
BenMorel
  • 34,448
  • 50
  • 182
  • 322
arena-ru
  • 990
  • 2
  • 12
  • 25
  • 1
    Perhaps if the exception is expected, it isn't an exception? Personally I think their design choice is warranted. If you are in control of the observable, on an error you could always consume it internally and yield a type representing "missing data" or "on error resume" or something, then when you get an exception you *didn't* expect the Rx framework won't just continue as if nothing happened. – Adam Houldsworth Nov 09 '12 at 15:10
  • @AdamHouldsworth only consumer knows if exception is expected or not. The solution that I showed is what you're saying but it doesn't look like elegant one. – arena-ru Nov 09 '12 at 15:18
  • The observable itself shouldn't care. The code that feeds the observable should know if it's an issue or not. If it's not an issue but the consumer wants to treat it as such then they can do so later. The code should still fail reliably on stuff you aren't expecting, so only cover the stuff you *are* expecting. Unfortunately, if an exception is to be returned in the middle of other types you can only a) create a containing return type for exceptions and proper data or b) use the Special Case class pattern for returning types that represent exceptions. – Adam Houldsworth Nov 09 '12 at 15:34
  • 2
    The community Extensions to Rx (Rxx) have this feature in their Either monad. Perhaps a better option is to keep in the spirit of the Rx semantics and use a nested observable sequence. If one fails, the next sequence starts. You can use operators like Switch/Merge/Concat with this style sequence – Lee Campbell Nov 14 '12 at 18:30

3 Answers3

7

If the consumer is the one that knows if the Exception is expected or not, then I say that throwing exceptions is incorrect here.

You're essentially trying to convey state or logic, which exceptions are not meant to do. Exceptions are meant to bring the system (or at the very least, the current operation) to a grinding halt because something has happened which the code doesn't inherently know how to recover from.

In this case, it just so happens that an Exception is part of your business logic/state, but that doesn't mean you can throw them. You need to pass them downstream to your consumer and then have your consumer process them.

A Tuple<T, Exception> would work in a pinch here, but the lack of specificity around the type (as well as the properties) is usually messy, so I'd recommend creating a dedicated type to convey the results of your operation and is exposed through the IObservable<T>.

casperOne
  • 73,706
  • 19
  • 184
  • 253
  • The implementation I have at the moment is similar to what you say, I'm looking to something more elegant, may be with help of `.Materialize()` extension like here for logging http://leecampbell.blogspot.com/2010/05/rx-part-4-flow-control.html – arena-ru Nov 12 '12 at 10:30
  • 3
    @arena-ru He's basically expanding on what I've said here but the point remains the same: if your logic depends on `Exception`, then you have to decouple the `Exception` from them being thrown, throwing `Exception` to handle *logic* errors is *never* a good idea. If an `Exception` is the correct data structure to hold your data and you need to pass information on it, then you need to return it/fire it, not *throw* it. – casperOne Nov 12 '12 at 12:28
1

OnError is meant to be a message that an unrecoverable error occurred so that the stream should be reset. After the recovery, next events don't make sense in the context of events that happened before the exception.

For example, consider the stream of events:

  • User joined the chat
  • User joined the chat
  • !! Chat crashed so all users are kicked
  • User joined the chat

Now the stream consumer use the Aggregate function to display the number of chatters. What should be displayed at the end? Of course 1, not 3. We should start counting from the scratch after the exception.

Bartłomiej Szypelow
  • 2,121
  • 15
  • 17
0

ValueOrException looks like Either type from functional programming. You have just swapped Left and Right. By convention Right is used for success and Left for failure.

petrn
  • 195
  • 2
  • 8