Jon Comtois
Jon Comtois

Reputation: 1854

What is the ReactiveUI way to handle exceptions when executing inferior ReactiveCommands?

Here is a (over) simplified version of what I am trying to demonstrate:

var reactiveCommandA = ReactiveCommand.CreateAsyncTask(_ => CanPossiblyThrowAsync());
reactiveCommandA.ThrownExceptions
                .Subscribe(ex => UserError.Throw("Oh no A", ex));

var reactiveCommandB = ReactiveCommand.CreateAsyncTask(_ => CanAlsoPossiblyThrowAsync());
reactiveCommandB.ThrownExceptions
                .Subscribe(ex => UserError.Throw("Oh no B", ex));

var reactiveCommandC = ReactiveCommand.CreateAsyncTask
   (
     async _ =>
               {
                 await reactiveCommandA.ExecuteAsync(); // <= Could throw here
                 await reactiveCommandB.ExecuteAsync();
                 DoSomethingElse();
               }
    );

reactiveCommandC.ThrownExceptions
                .Subscribe(ex => UserError.Throw("Oh no C", ex));

So assume my background implementation for reactiveCommandA might throw an exception. That is OK, since I have subscribed to .ThrownExceptions and will theoretically notify the user and retry/fail/abort (not shown here for brevity). So it will not bubble up to the dispatcher.

So that is great when reactiveCommandA is executed by itself. However, I have reactiveCommandC which executes reactiveCommandA and reactiveCommandB. I also subscribe to its .ThrownExceptions. The problem I'm running into is that if I execute reactiveCommandC and reactiveCommandA implementation throws within it, it also causes reactiveCommandC to blow up. Then I'm notifying the user twice for the same root error becuase reactiveCommandA does its .ThrownExceptions thing, then reactiveCommandC does its .ThrownExceptions thing.

So is there a standard approach to this type of situation? Preferably something somewhat elegant, since I find the existing code fairly clean and I don't want to clutter things up or introduce spaghetti.

Things I have thought of:

Is there something slicker to do here? Thanks.

Upvotes: 3

Views: 2452

Answers (2)

Flagbug
Flagbug

Reputation: 2103

I'm a bit late to the party, but how about something like this?

bool shouldThrow = true;

var reactiveCommandA = ReactiveCommand.CreateAsyncTask(_ => CanPossiblyThrowAsync());
reactiveCommandA.ThrownExceptions
                .Where(_ => shouldThrow)
                .Subscribe(ex => UserError.Throw("Oh no A", ex));

var reactiveCommandB = ReactiveCommand.CreateAsyncTask(_ => CanAlsoPossiblyThrowAsync());
reactiveCommandB.ThrownExceptions
                .Where(_ => shouldThrow)
                .Subscribe(ex => UserError.Throw("Oh no B", ex));

var reactiveCommandC = ReactiveCommand.CreateAsyncTask
   (
     async _ =>
               {
                 shouldThrow = false;

                 try
                 {
                     await reactiveCommandA.ExecuteAsync(); // <= Could throw here
                     await reactiveCommandB.ExecuteAsync();
                 }

                 finally
                 {
                     shouldThrow = true;
                 }
                 DoSomethingElse();
               }
    );

reactiveCommandC.ThrownExceptions
                .Subscribe(ex => UserError.Throw("Oh no C", ex));

Maybe not super elegant, but it should work in theory

Upvotes: 1

Ana Betts
Ana Betts

Reputation: 74654

Hm, this is an aspect of the ReactiveCommand design that kind of sucks (disclosure: I can say it sucks, I wrote it!) Here's the lazy way of fixing it:

Observable.Merge(rxCmdA.ThrownExceptions, rxCmdB.ThrownExceptions, rxCmdC.ThrownExceptions)
    .Throttle(TimeSpan.FromMilliseconds(250), RxApp.MainThreadScheduler)
    .Subscribe(ex => UserError.Throw("Oh no C", ex));

In the future I definitely want to fix some of these kind of double-throwing things, as well as a few other things about the RxCmd design.

Upvotes: 5

Related Questions