Skip to content

Investigate Observable Completion #23

@raquo

Description

@raquo

Currently Airstream has no concept of "completing" observables.

Completion is a special message emitted by an observable that notifies all observers – both internal and external – that the observable is done and will not produce any more events. Imagine onComplete() in addition to onNext(event) and onError(err).

This concept is a natural fit for event streams, but it needs to be adapted to signals. Unlike streams, Signals carry a current value, so for Signals completion also means that their current value will no longer change.

For example, Val-s would be completed on initialization. stream.take(1) would complete after one event. If Airstream had a take operator, that is. It doesn't, and I think we must at least have that much before we go all the way to implement completion.

The benefits of completion as a feature are not quite clear to me at the moment. I'm yet to see a real life pattern that requires completion. Which is not to say that such patterns don't exist, I just haven't run into them myself yet, or maybe I just tend to structure my code differently because I have Signals in Airstream. Not quite sure.

As for performance...

Completion will allow all child observables to remove themselves as internal observers from the completed observable. The completed observable can actually do this by itself without the completion feature, but what the completion feature allows is propagating this completion down the chain of observables – since an observable that only depends on completed observables is (generally) considered completed.

This chain reaction can result in early disposal of completed observables and subscriptions, which could potentially reduce memory usage (a subscription that looks at a completed observable can be killed without waiting for the owner to kill it).

One practical application where this could be useful is a pattern where an a parent component renders a dynamic list of children, and has an event bus that is sourced from streams generated by those children. With completion feature, we would complete the streams exposed by a child component when said child gets unmounted, the parent's event bus would be notified about that and would stop listening to this completed stream.

However, we already have a solution for this: eventBus.writer.addSource requires an Owner which will remove the source observable from event bus (e.g. when the component it belongs to is unmounted). Granted, this is a rather ad-hoc solution whereas completion would be a generalized solution to this problem.

But I'm not sure how useful completion is outside of this pattern. All the cases I ran into where I wished for completion for a performance gain involved firing one event before completing. That does not feel like much of an Observable to me, and I'm not sure if Observables should be optimized for what essentially is a singular callback pattern.

Is component.$mountEvents.collect { case ev: NodeDidMount => ev }.foreach(doStuff) all that better than a simple component.onMount(doStuff) callback? Is it such a big a problem that this stream does not stop after a NodeDidMount event was published (let's assume there will only ever be one such event)? Should this be solved differently, perhaps with a .once(doStuff), a new method that will unsubscibe the observer after one event?

Ultimately, observable completion is a big feature, and has complex interactions with other features. Without a clear need, it will remain unimplemented for now. I just wanted to get this out of my head.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions