Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TC39 Proposal - Observable #22

Open
krnlde opened this issue May 14, 2017 · 14 comments
Open

TC39 Proposal - Observable #22

krnlde opened this issue May 14, 2017 · 14 comments

Comments

@krnlde
Copy link
Contributor

krnlde commented May 14, 2017

The TC39 is going to standardize the way Observables should be used. Currently it is in Stage 1 (ready to advance to the next stage) https://github.com/tc39/proposal-observable

Maybe we should take action early on to match tko with the behavior of Observables in the proposal. Would be a huge advantage for tko when the proposal becomes a standard in 1 or 2 years.

@brianmhunt
Copy link
Member

Smart, thanks for linking

@krnlde
Copy link
Contributor Author

krnlde commented May 14, 2017

Could be done via:

import {observable as Observable} from 'tko';

const x = new Observable(/* TODO: API should be concise with the spec */);

@brianmhunt
Copy link
Member

There are some problems. For example, the TC Observable is not a function; the Knockout observable is essentially both the entity being observed and the observable class.

Which doesn't make it not doable, just interesting. :)

@brianmhunt
Copy link
Member

If we're going to be making an ES6 class of the TKO Observable (and arguably we should), here's a discussion maybe worth noting: http://stackoverflow.com/questions/36871299/how-to-extend-function-with-es6-classes

@krnlde
Copy link
Contributor Author

krnlde commented May 15, 2017

Maybe the ko.observable of the future is going to be obsolete and knockout will be the 2way databinding layer together with some utility functionality like computeds, validation, mapping, extend, susbscribable etc.

@brianmhunt
Copy link
Member

Yeah, this requires some thought, I believe. These are the priorities that come to mind:

  1. Maintain backwards compat
  2. Add new functionality for compatibility with TC
  3. Polyfill TC as needed (passing the proposal's tests)
  4. Add new functionality to supplement TC in ways that make sense for (t)KO (e.g. if we can reduce code duplication)

We've also got some code now that has Observable; we'll need to be cautious not to add more, owing to the name collision.

So the ko.Observable will become

  1. an TC-39 Observable, and
  2. also be the generator reader function i.e. when called with one argument it generates/triggers the subscriptions, when called with no arguments it returns the latest value.
  3. Lots of other things. :)

The ko subscriptions will need:

  1. an unsubscribe method. (=== dispose)
  2. a closed property

@krnlde
Copy link
Contributor Author

krnlde commented May 16, 2017

Great thoughts! I'll need some time to think about that too :)

@brianmhunt
Copy link
Member

@far-blue I'm copying your issue from tko.observable (and closing tko.observable issues as we've since/just moved to a monorepo here), as:

There's a standards proposal for ES8 (https://github.com/tc39/proposal-observable) which takes the bare bones of the observer/subscriber pattern from most.js, rxjs, xstream, zen-observable etc. to provide some compatibility in the same way Promises became standardised.

I don't think KO's observer/subscriber pattern is very far away from the suggested standard and it would be cool if KO observables could interop.

As an example of how these libraries work, I found this tutorial helpful :) https://gist.github.com/staltz/868e7e9bc2a7b8c1f754

As you can see, where the tutorial talks about streams of DOM events or subscribers updating the DOM, KO could slot in very neatly :)

@far-blue
Copy link

In the terminology used by libraries such as most.js, ko's observables are closest to 'Properties' rather than streams because they hold on to their last value. I'm not convinced KO needs to adopt Observables entirely, simply provide interop - at least to start with.

If you think about how KO would interop with Observables (aka 'streams'), I see three situations.

Firstly, a ko.observable could subscribe to a stream. the ko.observable would present as an Observer and so would need start(), next(), error() and complete() methods although error() and complete() methods could throw and be ignored respectively. start() and next() methods would simply update the value like the setter and trigger a hasMutated. This would work the same for writable computeds.

Secondly, a ko.observable would need to be observable through subscription. I'm not sure the constructor, the statics or the callebacks-based subscribe in the proposal are needed in this context and ko already has the concept of subscribe so extending it to support subscribing with an observer should be simple enough. The same applies to computeds.

Considering observableArrays, I'm not sure it would be a good idea to try to make them into streams. Instead, I suggest if an observableArray subscribed to a stream then it would simply update to match the latest value and expect the data values on the stream to already be arrays. In the same way, subscribing to an observableArray would mean the new state of the array was the pushed to the subscriber when the observableArray was changed. In the terminology of most.js and rxjs which treat arrays (Iterables) and promises as types of streams, an observableArray would generate a metastream but it's then up to the subscriber to deal with it.

In theory it also sounds nice to maybe have a stream of changes for an observableArray but then you'd need to define the structure for each event (add, remove, update). If you did, then you could also subscribe the observableArray to such a stream and have it apply the changes.

In all cases, I don't believe the standards proposal requires the Observer or Observable to be objects. As long as the methods are supported everything should work. As such, it should work if the required methods were in the function prototype for the functions returned by ko.observable() etc.

@far-blue
Copy link

To try and give some code examples.

First, a ko.observable tracking an Observable:

var foo = ko.observable();
var stream = Observable.from([1, 2, 3, 4]);
var subscriptionHandler = stream.subscribe(foo);

foo will be set to 1 then 2, then 3, then 4 in sequence.

An Observable from a ko.observable using from():

var foo = ko.observable();
var stream = Observable.from(foo);

@far-blue
Copy link

I've put together an example extender for ko.observable that works with most.js:
https://gist.github.com/far-blue/a652029beaaf9b7a97455c0d9cdd8678

@far-blue
Copy link

far-blue commented May 20, 2017

Just to comment on the gist. Clearly things would be neater if done natively rather than as an extender but the gist proves the interop suggestion. In the end it turns out (at least for most.js) observe() accepts a function that is called on each update of the thing it is observing so for the demo I just rely on a ko.observable being a function and passing it straight to observe(). For proper spec. interop it would prob. be sensible to have the Observer interface of next() and error() methods (next.js docs suggest complete() is deprecated) so the subscribe() method in the TC can be used rather than observe().

@far-blue
Copy link

I've investigated the ability to subscribe() and compatibility seems to come down to each library's interpretation of 'object'. RxJS and ZenObservable consider a function to be an object while most.js currently doesn't. ZenObservable does the check as Object(subscriber) === subscriber while most.js does typeof subscriber === 'object'. I think ZenObservable's approach to be better and will submit an issue for most.js.

I've updated the gist to demonstrate how subscribe() could work if most.js followed ZenObservable's approach but that means the gist now only works if you update line 1075 of most.js (non-minified!) from if (subscriber == null || typeof subscriber !== 'object') { to if (subscriber == null || Object(subscriber) !== subscriber) {

The gist also renamed what was the "Observable" extender to "Observer" and created a second extender for the actual "Observable".

@far-blue
Copy link

FYI, most.js v1.4.0 includes my patch to allow functions as subscribers :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants