I'm working on a Cycle JS app using storage and state plugins.
The app use xstream as reactive library.
I'm incapped in a strange behaviour.
My streams are as this diagram
On first session var update I got this debug result:
PROBLEM: "Debug 2" branch is not executed
If I update the session item (so to produce a new storage event) the two branch ar all executed as expected
The same good behavior happen happen if add .remember() at "debug 0"
Even stranger, the flow work as expected if I remove filter
Without Filter (and without remember) the flow give this result since the first event
My suspect is something observe at "debug 0" before the second branch is attached and so the first event is already consumed. But how can this happen if the two branch are xs.merged together? How can one branch be executed and second not? The two branch dosen't got filters or other processing, they mererly mapTo a reducer function. Any suggestion on how to debug and resolve this situations?
The problem happens because storage.session.getItem
emits as soon as it's subscribed, and merge(a$, b$)
subscribes to a$
before it subscribes to b$
, so a$
gets the event, but by the time b$
is subscribed, it has arrived too late. This problem is well known and is called a glitch (in reactive programming), and usually occurs when there is a diamond shaped stream graph, which is exactly your case.
I have two blog posts about glitches which can give you more context: Primer on RxJS schedulers and Rx glitches aren't actually a problem. It mentions RxJS, but xstream is quite close to RxJS in terms of implementation. The difference between xstream and RxJS is that xstream streams are always multicast ("shared"), and RxJS has many scheduler types, but xstream has only one. The default scheduler in RxJS is the same behavior as xstream has.
The solution is to apply .remember()
before reaching the diamond. This is because the value being emitted needs to be cached for other consumers of that stream. .remember()
simply converts the Stream to a MemoryStream. I think the source stream was initially a MemoryStream, and mapping a MemoryStream creates other MemoryStreams, but filter
is an operator that breaks that. filter
always returns a Stream, and the reason for this is that MemoryStreams should always have a current value, but filter
can remove values, so it's possible that a filtered stream would not have any current value.
As the author of Cycle.js I believe the way cyclejs/storage is designed is not the best, and I think we will find ways of designing these APIs so that confusion with MemoryStream versus Stream is minimized. But for the time being, it's important to understand the difference between these two and plan your app so to avoid diamonds (and glitches), or to use .remember()
in the right spots.