Autorun
Autorun
can be used in those cases where you want to create a reactive function that will never have observers itself. This is
usually the case when you need to bridge from reactive to imperative code, for example for logging, persistence, or
UI-updating code. When the Autorun
extension method on ISharedState
is used, the provided function will always be
triggered once immediately and then again each time one of its dependencies changes. In contrast,
Computed creates functions that only re-evaluate if it has observers on its own, otherwise its value is
considered to be irrelevant. As a rule of thumb: use Autorun
if you have a function that should run automatically but
that doesn't result in a new value.
Use Computed
for everything else. Autoruns are about initiating effects, not about producing new values.
You can pass an instance of AutorunOptions as an argument to Autorun
, it will be
used to set options like a debug name or a delay.
The return value from autorun is an IDisposable
instance, which can be used to dispose of the autorun when you no
longer need it. The reaction itself will also be passed as the only argument to the function given to autorun, which
allows you to manipulate it from within the autorun function. This means there are two ways you can dispose of the
reaction when you no longer need it:
using Cortex.Net.Api;
using Cortex.Net;
var sharedState = SharedState.GlobalState;
var disposable = sharedState.Autorun(reaction =>
{
/* do some stuff */
});
disposable.Dispose();
// or
sharedState.Autorun(reaction =>
{
/* do some stuff */
reaction.Dispose();
});
Just like the Observer
attribute, Autorun
will only observe data that is used during the execution
of the provided function.
using Cortex.Net.Api;
using Cortex.Net;
var sharedState = SharedState.GlobalState;
var numbers = sharedState.Collection(new[] { 1, 2, 3 });
var sum = sharedState.Computed(() => numbers.Sum());
// prints '6'
var disposable = sharedState.Autorun(() => Console.WriteLine(sum.Value));
// prints '10'
numbers.Add(4);
disposable.Dispose();
// won't print anything, nor is `sum` re-evaluated
numbers.Add(5);
Options
Autorun accepts as the second argument an AutorunOptions instance with the following options:
Delay
: Number in milliseconds that can be used to debounce the effect delegate. If zero (the default), no debouncing will happen.Name
: String that is used as name for this reaction in for exampleSpy
events.ErrorHandler
: delegate that will handle the errors of this reaction, rather then propagating them.Scheduler
: Set a custom scheduler to determine how re-running the autorun function should be scheduled. It takes a async delegate that should be invoked at some point in the future, for example:{ Scheduler: async () => { await Task.Delay(1000); }}
The Delay
option
using Cortex.Net.Api;
using Cortex.Net;
using System.Text.Json;
var sharedState = SharedState.GlobalState;
sharedState.Autorun(
r => {
// Assuming that profile is an observable object,
// send it to the server each time it is changed,
// but await at least 300 milliseconds before sending it.
// When sent, the latest value of profile will be used.
sendProfileToServer(JsonSerializer.Serialize(profile));
},
new AutorunOptions() { Delay: 300 }
);
The ErrorHandler
option
Exceptions thrown in autorun and all other types reactions are caught and logged to the console or debugger, but not propagated back to the original causing code. This is to make sure that a reaction in one exception does not prevent the scheduled execution of other, possibly unrelated, reactions. This also allows reactions to recover from exceptions; throwing an exception does not break the tracking done by Cortex.Net, so as subsequent run of a reaction might complete normally again if the cause for the exception is removed.
It is possible to override the default logging behavior of Reactions by providing the ErrorHandler
option
Example:
using Cortex.Net.Api;
using Cortex.Net;
var sharedState = SharedState.GlobalState;
const age = sharedState.Box(10);
const dispose = sharedState.Autorun(
r =>
{
if (age.Value < 0)
{
throw new Exception("Age should not be negative");
}
Console.WriteLine($"Age: {age.Value}");
},
new AutorunOptions() {
ErrorHandler = (r, e) => {
Debug.WriteLine($"Invalid age in reaction. Exception: {e.Message}");
}
}
);
A global onError handler on the shared state can be set as well, use ISharedState.UnhandledReactionException. This can be useful in tests or for client side error monitoring.