mrpmorris / Fluxor

Fluxor is a zero boilerplate Flux/Redux library for Microsoft .NET and Blazor.
MIT License
1.24k stars 141 forks source link

Support CancellationToken in EffectMethod? #306

Closed StefH closed 2 years ago

StefH commented 2 years ago

I'm not sure if it makes sense / if it's possible to support also a CancellationToken in an EffectMethod?

Example:

[EffectMethod]
public async Task HandleFetchDataAction(FetchDataAction action, IDispatcher dispatcher, CancellationToken cancellationToken)
{
  await Task.Delay(2000, cancellationToken);
}
mrpmorris commented 2 years ago

Where would the token come from?

IDispatcher.Dispatch is synchronous and doesn't (and shouldn't) wait for effects.

StefH commented 2 years ago

Consider this:

  1. User is on the 'FetchWeather' page, and the weather is fetched from en external service. This takes a long time.
  2. User decides to move to another page
  3. It has no use to keep fetching the data, so the call via the HttpClient can be cancelled.

Would this be something which is a valid scenario?

uhfath commented 2 years ago

Where would the token come from?

IDispatcher.Dispatch is synchronous and doesn't (and shouldn't) wait for effects.

Perhaps something like IDispatcher.Dispatch(new SuperAction(...), cancellationToken) ? Same as MediatR does. It receives the token from the caller and transfers it all the way to the request handler: https://github.com/jbogard/MediatR/blob/8f7b236a7a1a09ded9bc0f2e2ffbdaefab49a1de/src/MediatR/Mediator.cs#L28

mrpmorris commented 2 years ago

When multiple EffectMethod attributes are detected on a single concrete class, Fluxor will create one instance shared instance (within the DI container scope) for all of them, so you can share state like this

public class SomeSearchEffects
{
    public CancellationTokenSource? TokenSource;

    // Cancel any previous execution, start a new one
    [EffectMethod]
    public async Task HandleSearch(SearchAction search, IDispatcher dispatcher)
    {
        Cancel();
        TokenSource = new CancellationTokenSource();
        var someApiResponse = await CallSomeApi(CancellationTokenSource.Token);
        if (TokenSource?.Token?.IsCancellationRequested == false)
          dispatcher.Dispatch(.............);
    }

    // If anyone navigates anywhere, ensure it's cancelled. Needs FluxorOptions.UseRouting()
    [EffectMethod(typeof(GoAction))]
    public Task HandleGo(IDispatcher _)
    {
        Cancel();
    }

    // Or maybe you explicitly want to cancel when the user clicks a [Cancel] button?
    [EffectMethod(typeof(CancelSearchAction))]
    public Task HandleCancel(IDispatcher _)
    {
        Cancel();
    }

    private void Cancel()
    {
        TokenSource?.Cancel();
        TokenSource = null;
    }
}
uhfath commented 2 years ago

I'm not sure this is the case. The point was in using an external cancellation token, e.g. the one from the page component to cancel effect operation once a user navigates away from it (at least that's what I'm thinking about). Of course it's doable by simply declaring the token in each action (or inheriting from one with it) but an out-of-the-box solution would be nicer.

mrpmorris commented 2 years ago

I don't really understand. As you know, Blazor pages don't navigate away, the same page re-renders a new route and changes the brower's URL.

Do you have an example of a specific problem I can look at?

StefH commented 2 years ago

Hello @mrpmorris,

See this link what I mean by 'user-navigates-away': https://www.meziantou.net/canceling-background-tasks-when-a-user-navigates-away-from-a-blazor-component.htm

mrpmorris commented 2 years ago

My suggestion will handle that scenario.

The other option you have is to pass the token with the action, and the Effect can use that.