cyclejs: Solve memory leak with circular dependencies of streams
This is an issue I keep getting stuck at in different context. When the parent needs to listen of events of the child, how do you break or avoid a cycle?
e.g.
function List(sources) {
const actions = intent({ ...sources, click$: ??? })
const state = model(actions).shareReplay(1)
const items$ = state
.flatMapLatest(({ items, selected }) =>
items.map(item => Item({
...sources,
props$: Observable.of({ id: item.id, selected: item.id === 'selected' })
}))
const click$ = items$.map(items => Observable.merge(...items.map(item => item.click$)))
return {
...
}
}
Basically the following flow:
click => List => selected => Item => click
Which gives me a cycle. How do I get around this? I could use a proxy as I’ve seen in some examples:
const clickProxy = new Subject()
const actions = intent({ ...sources, click$: clickProxy })
const subscription = click$.subscribe(clickProxy)
// subscription leaks...
However, that gives me a memory leak…
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Comments: 34 (17 by maintainers)
I believe leak with proxy subject can be avoided using
finallythat catches any termination of the stream:https://jsfiddle.net/jm4ke9h0/
babelplugin (https://github.com/cyclejs/core/issues/170) definitly will be needed to get rid of this dirt)usingconstruction is quite clumsy, especially if multiple proxy will be needed in function.@staltz: This doesn’t feel quite solved.
Take the following more advanced example:
What stream do you hook up with
Observable.using. With my suggestion it’s kind of undefined and I am confused on how to apply your suggestion on this, since your example only returns a single sink.i.e. the fundamental conceptual questions is, who owns the subscription? Is it the DOM?
In that case we can create a more generic helper:
Which would give us:
I wonder whether it is enough to just dispose
click$or whether one has to dispose the actual subscription.I’m labeling this issue as a discussion, because xstream has built-in solution for this. But I’m not “closing” the comments, I still think this is a good discussion and resource to read/re-read.
Just curious, would it work to create a
circularSubscribemethod (with a better name though), so you can replacelet proxySubscription = source.subscribe(proxySubject)bysource.circularSubscribe(proxySubject), and with :This way, when
proxySubjectcompletes, the subscription is disposed.Updated comment above with helper suggestion.
For reference, here is how I would have done a
Listcomponent using previously suggested helpers: https://gist.github.com/ronag/9f9fdfb7c6169e6850c4