- From: Ingar Mæhlum Arntzen <ingar.arntzen@gmail.com>
- Date: Tue, 9 Jun 2015 16:35:36 +0200
- To: Francois Daoust <fd@w3.org>
- Cc: public-webtiming@w3.org
- Message-ID: <CAOFBLLrEACs2ubrMW5N_FT0gZ3qtz47hN0qRpjfUZP=Nmg-n0Q@mail.gmail.com>
Hi Francois and all. Your post on association between timing objects and timing providers opens the floor for several discussions (by intention I suspect :) ). I'll try to highlight one issue here, and leave others for later. >From the programmer perspective, it is quite tedious to have to wait for an object to reach some state of readiness, before you can start to use it. In this perspective, it would be nice if timing objects may be intantiated/generated up front as "empty shells", yet allowing application handlers to be safely registered immediately. As bootstrapping is finalized, the "empty shells" are filled up, and events are being emitted as expected - causing application code to start executing. The requirement to provide this guarantee would have to be reflected in the spec somehow. I'm not sure how though - maybe there is some precedence in other event interfaces? Related to this, there is one more detail for the event interface that Njål and I would like to propose. Quite often in Web interfaces, you need to check whether an event has already been emitted or not. For instance, consider the "onopen" event. To be sure of correctness you often end up with code like this if (is_open()) { do_your_thing (); } else { onopen = function () {do_your_thing();} } Francois, you have a very similar example in your example code in this thread. Now, this may not look like much of an inconvenience, but it adds up. This may have to be done for multiple event types, and again recursively in higher-level api's that depend on underlying events. In my experience, it quickly gets difficult to reason about correctness as well as tracking down bugs. These scenarios may even lead to nasty effects such as event duplication or reordering. This problem also generalizes to "observing" or visualizing any object that has state and may be subject to modification. // visualize the state of an object var state = o.get_state(); visualize(state); // attach handler for future state changes o.on("change", function () { var state = o.get_state(); visualize(state); }) We have successfully avoided this complexity (for the event consumer) by a subtle change in the eventing api. Basically we are shifting the responsibility for sorting this out from the event consumer (examples above) to the event source. So, when an event consumer attaches a handler, the event source is responsible for sending an initial event with the current state, before any subsequent event. This makes life very simple for the consumer/programmer. Simple statements like onopen = do_your_thing; or o.on("change", function () { var state = o.get_state(); visualize(state); }) are sufficient. There is less code duplication and if/else constructs. Also, the mental model is somewhat simplified. You can always trust the event source to give you the whole story. I suppose this pattern has a name but I'm not aware of it - sorry :( Finally, this idea ties in neatly with the bootstrapping issues discussed above. timing_object.on("change", function () {//react}) will be an entry point for all timing sensitive app code, and it will always work, independent of the state of the timing_object at the time when the app handler is registered. Again, I'm not sure how to include this in the spec. Ideally, if this pattern was documented in some specific EventListenerInterface we could simply reference that - but I suspect it is not? Also, to explain the (slightly more complex) logic of the event source, we could provide some sample code if needed. Best, Ingar 2015-06-08 17:18 GMT+02:00 Francois Daoust <fd@w3.org>: > Hi again, > > Looking at the TimingObject interface as defined in the spec [1], I'm > wondering what would be the proper way to associate a TimingObject instance > with an online timing service and to start using it. > > The spec currently currently defines an under-specified "src" attribute > but I'm wondering whether it's of any use in the end: using a URL would > probably require that we define a custom URL scheme to tell the user agent > about the protocol to use. Since we want to leave some freedom for online > timing services to define their own mechanisms, that does not seem to be a > good approach and I would just drop it for now. > > I explored the possibility to use an "srcObject" attribute to associate a > local instance with a TimingProvider instance connected to an online timing > service in my prototype [2]. However, this means that, before being able to > use the timing object, the code needs to listen to the "readystatechange" > event, which is somewhat tedious. > > Also, "srcObject" could be re-assigned at any time, so the code needs to > take that possibility into account. > > var runWhenReady = function () { [[...]] }; > var provider = [[get timing provider instance]]; > > var alreadyRun = false; > var timing = new TimingObject(); > timing.srcObject = provider; > timing.onreadystatechange = function (evt) { > if (!alreadyRun && (evt.value === 'open')) { > alreadyRun = true; > run(); > } > }; > > To simplify the life of developers, it might be better to define a > dedicated "open" event (as in the WebSockets API) instead of a > "readystatechange" event and, assuming the TimingProvider has a simple > (connecting, open, closing, closed) lifecycle, to prevent the timing object > from being associated with more than one TimingProvider, e.g. by dropping > the srcObject attribute and rather passing the TimingProvider instance as > part of the constructor, as in: > > var timing = new TimingObject(provider); > timing.onopen = runWhenReady; > > Perhaps more fashionable, there could be a ready() method that returns a > Promise that resolves when the association is ready and rejects when > connection goes wrong: > > var timing = new TimingObject(provider); > timing.ready().then(runWhenReady); > > ... but I think I still prefer the "onopen" mechanism. Any thought? > > > The "TimingProvider" interface would also need to be specified. In the > end, all I needed was something pretty close to the TimingObject interface: > > interface TimingProvider : EventTarget { > readonly attribute DOMString readyState; > readonly attribute StateVector vector; > readonly attribute Interval range; > attribute EventHandler onreadystatechange; > StateVector query(); > void update(StateVector newVector); > void close(); > } > > I'm not entirely convinced that the "vector" attribute needs to be exposed > in practice but that's a minor detail. > > > The interface is so close to TimingObject that it begs the question as to > whether a timing object associated with an online timing service should not > be seen as a derived TimingObject class rather than as a TimingObject > associated with a TimingProvider. The only real difference is the > "timeupdate" event (and a few helper methods that could also be added to a > TimingProvider). I guess it makes sense to keep a distinction if user > agents implement the TimingObject interface natively whereas TimingProvider > classes may be defined in regular JS libraries. > > Should I sketch some text along these lines for the TimingProvider > interface? > > Francois. > > > [1] http://webtiming.github.io/timingobject/ > [2] https://github.com/tidoust/timingservice > >
Received on Tuesday, 9 June 2015 14:36:12 UTC