Abbruch
Deja bindet jede Ausführung an die Lebensdauer der besitzenden Komponente und bricht verdrängte Ladevorgänge automatisch ab — meist ganz ohne Verdrahtung.
Der implizite Token#
Innerhalb einer DejaComponentBase-Komponente verwendet jede Query- und
Mutation-Ausführung standardmäßig den Lebensdauer-Token der Komponente. Beim Wegnavigieren
wird die Komponente disposed, das bricht den Token ab, und das bricht den Request ab — bis
hinunter zum Server. Deine Fetch-Funktion muss den Token nur durchreichen:
// Nothing to wire: the token is the component's lifetime token.
protected override Task OnInitializedAsync()
=> _todos.Execute("todos", token => Api.GetTodosAsync(token));
Ein Fetch, der Abbruch nicht beobachten kann, ignoriert den Token per Discard:
QueryFunction = _ => LegacyFetchAsync(). Deja verwirft sein veraltetes
Ergebnis trotzdem, wenn ein neuerer Ladevorgang übernommen hat — nur den Request auf der
Leitung kann es dann nicht abbrechen.
Ein Token pro Aufruf ersetzt den impliziten#
Wer CancellationToken auf den Parametern setzt, bindet eine Ausführung an etwas
Engeres als die Komponente — etwa die eigene Source eines Suchfelds. Der Token
ersetzt den Komponenten-Token; er wird nicht mit ihm kombiniert:
_search.Execute(token => Api.SearchAsync(term, token), p =>
{
p.CancellationToken = _searchCts.Token; // replaces, not combines
});Verdrängen und Abbrechen (ungecachter Pfad)#
Ein Execute ohne Key bricht die vorige laufende Ausführung ab, bevor es startet.
Der ältere Request wird abgebrochen, seine Callbacks werden übersprungen, und sein
verspätetes Ergebnis — selbst aus einem Fetch, der den Token ignoriert hat — kann die Daten
des neueren Ladevorgangs nie überschreiben. Abbruch ist kein Fehler: kein Fehlerzustand,
keine Callbacks; der verdrängende Ladevorgang treibt das nächste Update.
Der subtile Fall: Abbruch auf dem gecachten Pfad#
Ein Fetch mit Key gehört zum Cache-Eintrag, und andere Komponenten warten womöglich darauf. Der Token eines Aufrufers wird deshalb bewusst nicht in den gemeinsamen Fetch verknüpft: Ihn abzubrechen löst diesen Aufrufer ab, ohne den Request abzubrechen. Das Unmounten einer Komponente darf nicht die Daten einer anderen abbrechen. Der gemeinsame Fetch selbst wird erst abgebrochen, wenn der letzte Subscriber den Eintrag verlässt.
ComponentToken für eigene Aufrufe#
Arbeit, die Deja nicht für dich ausführt — ein direkter API-Ping aus einem Event-Handler — kann trotzdem die Lebensdauer der Komponente teilen:
private async Task OnClick()
=> await Api.PingAsync(ComponentToken);Live-Demo#
Last request started: #0
No result yet.
Raise the latency in the demo controls, then click the start button rapidly. Every click supersedes — and cancels — the in-flight request, so the result on screen is always the latest click, never a slow stale response arriving late. Navigating away cancels via the component's lifetime token with zero wiring.
@inherits DejaComponentBase
@inject JsonPlaceholderApi Api
<div class="demo-toolbar">
<button class="demo-button primary" @onclick="LoadNext">
Start request #@(_requested + 1)
</button>
<button class="demo-button danger" @onclick="() => _todo.ClearData(cancelCurrentRequest: true)">
ClearData(cancel: true)
</button>
</div>
<StateInspector Query="_todo" Label="Unkeyed query"/>
<div class="demo-panel">
<h4>Last request started: #@_requested</h4>
@if (_todo.Data is { } todo)
{
<p>Result on screen: <strong>#@todo.Id</strong> — @todo.Title</p>
}
else
{
<p class="demo-note">No result yet.</p>
}
</div>
<p class="demo-note">
Raise the latency in the demo controls, then click the start button rapidly. Every click
supersedes — and cancels — the in-flight request, so the result on screen is always the
<em>latest</em> click, never a slow stale response arriving late. Navigating away cancels via
the component's lifetime token with zero wiring.
</p>
@code {
// Unkeyed on purpose: the supersede-and-cancel behaviour belongs to the uncached path.
private readonly Query<TodoDto> _todo = new();
private int _requested;
private Task LoadNext()
{
var id = ++_requested;
return _todo.Execute(token => Api.GetTodoAsync(id, token), p => p.OnError = _ => { });
}
}