Ihr wollt vielleicht kein RxJS
State-Management-Lösungen sind in der Regel an einen bestimmten Change-Propagation-Mechanismus gebunden. Das ergibt sich meistens aus dem, was das Framework am stärksten unterstützt. Für Angular wäre das RxJS, für Vue.js die Composition API. React erzwingt keine Lösung und lässt damit viel Raum zur Erkundung. Die beliebtesten sind dennoch mit RxJS geschrieben.
Ich bin jedoch nicht davon überzeugt, dass RxJS für State-Management-Lösungen verwendet werden sollte. Ein Store ist im Wesentlichen ein BehaviorSubject mit einigen Annehmlichkeiten drum herum. Der folgende Code reicht daher aus, um den Grund für meine Sichtweise zu demonstrieren:
const store$ = new BehaviorSubject(someValue);
const derived$ = store$.pipe(
// ... some operators later
switchMap(async value => {
return await asyncCall(value);
})
);
// somewhere else
const derivedValue = await firstValueFrom(derived$);
Könnt ihr den Bug hier erkennen? Wenn wir versuchen, den Wert aus dem Observable zu holen, lesen wir möglicherweise veraltete Daten, weil das Promise noch nicht aufgelöst wurde. Es ist wie bei letztendlich konsistenten Datenbanken, bei denen ein Lesen direkt nach dem Schreiben nicht garantiert, dass die neuesten Änderungen verfügbar sind.
Das führt dazu, dass manchmal inkorrekte Daten in der Produktion konstruiert und persistiert werden, die in der Entwicklungsumgebung kaum reproduzierbar sind, weil die asynchronen Aufrufe schnell genug aufgelöst werden.
Die Grundursache liegt darin, dass RxJS versucht, Asynchronität zu abstrahieren, aber kein System hat, um Downstream mitzuteilen, dass ihr Zustand ungültig geworden ist. Es gibt keine Möglichkeit zu wissen, ob der Wert, den wir gerade gelesen haben, wirklich aktuell ist.
Warum möchten wir den Wert aus dem Observable holen? Das Lesen von Daten aus dem Store und das Arbeiten damit bei Benutzeraktionen ist sehr verbreitet. Das gilt insbesondere für große und komplexe Anwendungen, bei denen mehrere Abstraktionsschichten auf dem Store mit RxJS-Pipes und Kombinatorik aufgebaut werden.
„Das ist nicht der reaktive Weg”, könntet ihr sagen – und damit habt ihr absolut recht. Der reaktive Weg löst jedoch unsere realen Anforderungen nicht.
Betrachtete Lösungen
Es wurden viele Ideen in Betracht gezogen, wie dieses Problem behoben werden kann, und keine davon ist ohne Nachteile.
Benutzerinteraktion blockieren
Die Idee besteht darin, die Benutzerinteraktion zu blockieren, bis die Aktion abgeschlossen ist. Abgesehen von der schlechten UX könnte ein combineLatest irgendwo die Anwendung zu früh entsperren. Der Grund, warum das auftreten kann, liegt daran, dass combineLatest selbst veraltete Daten verwendet.
Verarbeitungs-Queue für Benutzeraktionen
Ein weiterer ähnlicher Ansatz ist das Einreihen von Benutzeraktionen in eine Queue, das Warten auf den Abschluss der vorherigen Aktion und dann das Starten der Verarbeitung der nächsten. Da die nächste Aktion möglicherweise Daten aus den Abstraktionen lesen muss, die wir mit RxJS aufgebaut haben, kann das nur funktionieren, wenn wir wissen, wann der Zustand konsistent geworden ist – was genau das Problem ist, das wir überhaupt zu lösen versuchen.
Keine Lesevorgänge nach Schreibvorgängen
Das Schreiben in den Store ist nur am Ende der Aktion erlaubt. Das verhindert das Lesen veralteter Daten im Kontext dieser Aktion. Was aber, wenn der Benutzer sehr schnell klickt? Wir würden veraltete Daten erhalten, die durch die vorherige Aktion ordnungsgemäß hätten aktualisiert werden sollen.
Promises nicht „entfärben” erlauben
Wir würden Observable<Promise<T>> anstelle von Observable<T> erhalten, und alle Operatoren wie debounceTime, die Kombination von switchMap mit async-Funktionen wären verboten. Die Änderungen werden sofort an Downstream weitergegeben und erfordern nur zusätzliche awaits. Das ist aus meiner Sicht die attraktivste Lösung. Der Nachteil wäre, dass viele Features von RxJS deaktiviert werden und ein erhöhter mentaler Overhead entsteht, weil Promises und Observables gemischt werden.
Gibt es eine Möglichkeit, das Verhalten in RxJS selbst zu korrigieren?
RxJS könnte eine Möglichkeit implementieren, Downstream-Daten für ungültig zu erklären. Es gäbe jedoch viele Randfälle. Was sollte das Verhalten von etwas wie shareReplay(10) sein? Was sollte dort für ungültig erklärt werden? Ich denke, dieser Ansatz würde zu einem grundlegenden Redesign von RxJS führen und ist möglicherweise nicht praktikabel.
Abschließende Worte
Ich bin offen für Ideen, wie wir mit diesem Problem angemessen umgehen können. Bis dahin würde ich keine RxJS-basierten State-Management-Lösungen für Anwendungen von vernünftiger Größe empfehlen.
Agent Hub
Gehen Sie über vereinzelte AI-Piloten hinaus und schaffen Sie eine sichere, skalierbare Grundlage für Agentic AI in Ihrer gesamten Organisation.
Zur Demo




