Airstream semantics update – RFC
See original GitHub issueI am currently reworking Airstream behaviour in several big ways. Most of these changes will be released soon as 0.15.0, and will definitely affect your code. Please read this and provide feedback to make sure that the changes account for your use cases. If anything below sounds controversial or is devastating to your use case, please let me know. If anything sounds helpful, please let me know too. Much of this is very hard, so possibly not all of this will happen right away, so I need to know what to prioritize.
No more ==
checks in signals
This is a solution to #19 – signals won’t compare the new value against the previous value before emitting anymore. This can break your code if you rely on signal’s events being unique / deduplicated. Solution is to use the new distinct
/ distinctBy
methods to add back uniqueness as needed. Laminar itself does not need you to do it, but you might need this to avoid making redundant Ajax requests or other side effects.
Signals will re-evaluate their current value after being re-started
This is a solution to #43.
Main caveat: only signals have a current value, so only signals that depend on other signals can do this. If your signal is stream.startWith(1)
, not much you can do about having missed events from stream
, since streams don’t replay their events for new subscribers. Same for stream.foldLeft(...)
Other caveat: there is no way SIGNAL.foldLeft(...)
will fall neatly into either old or new behaviour. I will probably need to make its behaviour configurable.
Observables will stop resetting their internal state when they’re stopped
For example, when stream1.combineWith(stream2)
is stopped and started again, currently it won’t start emitting again until both stream1 and stream2 emit a value, because it “forgets” the previous events and is returned to an empty state similar to how it was originally created. That was the old idea, and I think we’re done with it now.
Similarly, when a streamOfStreams.flatten
is stopped, it “forgets” which stream it was supposed to mirror, so when it starts up again it does nothing until streamOfStreams
emits again.
Things like this really mess with your ability to re-mount Laminar components. The idea with all these changes is that with the new default behaviour you should be able to re-mount a component and it should start working again from where you left off, pretty much, perhaps updating some signals’ data if possible (see above).
emitOnce
param default will be switched from false
to true
, or even removed (for streams like EventStream.fromValue). I’m thinking to maybe implement an evalOnStart(:=> myStream)
function that returns an observable that re-creates and mirrors myStream
every time it’s started. That would help with a bunch of different streams like fromValue, periodic, fromFuture, etc.
Anything that has a resetOnStop
param will have it removed or default to false
(well I guess that’s just EventStream.periodic()`
The main edge case here are async streams like ajax / delay / debounce. They will probably get their own treatment, but not sure what it is yet. With async logic it’s really hard to come up with something that would work universally well, especially if you consider all the possible timing edge cases.
KeepAlive
One thing that annoys me about ownership lifecycles today it the inability to easily adjust them: #70
For example, say you make an ajax POST request using a stream, and you want to update your app’s data store or cookies or something once the request finishes. But what if the user unmounts the component where this logic is defined while the request is still in flight? If the cookie-updating logic was bound inside that component, it will be deactivated once the component is unmounted, and will never happen. Alternatively, if you use unsafeWindowOwner
for that logic, it might cause a memory leak if you’re not careful to dispose of the subscription, which is not something you should be worrying about with Airstream.
So I’m thinking to provide some kind of keepAlive(maxEvents, maxSeconds)
method that would keep the subscription running. I haven’t figured out how to actually implement it yet in order for it to be the most useful, whether it should be on the observer side or the observable side, or a property of the Subscription class exposed conveniently via some helper in Laminar.
Have you run into this issue yourself?
Split operator will always return a Signal (not a stream)
You can still call split
on streams, but it will return a signal whose initial value will be an empty collection. I went mad getting it to work properly, and now it does, but I don’t think I can implement a properly semantic stream for this. It’s really complicated. If you need a stream, just call .changes
(Laminar doesn’t care anymore so not sure why you’d need that). I guess Airstream could have done that for you but semantically the output of split is really a Signal as it relies heavily on its internal state.
Also, split
will always provide a Signal as the third param in the render callback.
Observable completion
I don’t think this will make it into v0.15.0, but while I have your attention, I will eventually also try to implement observable completion feature, see #23 #33 #1. Basically that means that once it’s known that an observable won’t ever emit any new values (not even after a restart), it proactively kills all of its subscriptions.
The interactions of observable completion feature with other features are really complicated. What I’d like from you is to tell me whether you want this feature, and if so, why. One argument I heard is that it will allow a concat
method on streams – if that’s the kind of feature that you want, please let me know how you plan to use it to help me visualize it better.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:4
- Comments:11 (5 by maintainers)
Top GitHub Comments
Unfortunately KeepAlive mechanics won’t make it into 0.15.0. I’ve looked into it, and it’s very doable, but it’s just too much work for this iteration. Current status as follows:
Done:
I’m currently wrapping up https://github.com/raquo/Laminar/issues/95. Really happy with how that feature worked out. Now onto “the second 90%”…
TODO for v0.15.0:
composeEventsFlat
splitByIndex
in a couple hours, I’ll do thatSo, the hard parts are done, but quite a lot of work still ahead. All of that will probably take me a few weeks.
@ngbinh That’s the plan, currently I have the following methods drafted: