Experimental implementation of Object.observe & JS Utility library now available

Rafael Weinstein rafaelw at chromium.org
Mon Aug 20 21:25:12 PDT 2012


On Mon, Aug 20, 2012 at 8:57 PM, John J Barton
<johnjbarton at johnjbarton.com> wrote:
>
>
> On Sun, Aug 19, 2012 at 3:31 PM, Rafael Weinstein <rafaelw at chromium.org>
> wrote:
>>
>> On Sun, Aug 19, 2012 at 11:25 AM, John J Barton
>> <johnjbarton at johnjbarton.com> wrote:
>> >
>> >
>> > On Fri, Aug 17, 2012 at 9:49 PM, Rafael Weinstein <rafaelw at chromium.org>
>> > wrote:
>> >>
>> >> >> A synchronous observation mechanism provides an attacker too many
>> >> >> opportunities for a plan interference attack. If you'll recall, an
>> >> >> earlier
>> >> >> synchronous proposal died for this reason.
>> >>
>> >> That is an excellent reason. I have two others:
>> >>
>> >> 1) It's a terrible design pattern to encourage. Webdevs will
>> >> absolutely use it and learn the hard way, just like we did with
>> >> Mutation Events that it works fine when we start, but down the road
>> >> lies nothing but pain and suffering.
>> >>
>> >> 2) Synchronous doesn't actually exist -- it's a false option.
>> >>
>> >> To explain: When people talk about "synchronous" what they expect is
>> >> that they will be notified concurrent with the event happening (this
>> >> *is happening*). The expectation is that when they are invoked they
>> >> are observing the world as the event is describing.
>> >>
>> >> This is the appeal of synchronous delivery. Here's the rub: unless you
>> >> plan to only allow a single observer per object, then you have to pick
>> >> someone to go first. And unless you plan to prevent that person from
>> >> further mutating the object, the next guy to be called may not observe
>> >> the world in the state suggested by the "synchronous" call.
>> >>
>> >> In fact, an arbitrary number of mutations can always have occurred by
>> >> the time any given observer is called, so it's just dishonest API
>> >> which pretends that a single thing has happened. The correct thing is
>> >> to inform each observer what *set* of things has happened.
>> >>
>> >> So the only questions are:
>> >>
>> >> 1) Do you mislead the consumer with API which suggests that only one
>> >> thing will has happened
>> >> 2) If not, when do you deliver the set of things: immediately after
>> >> the mutation occurs, at the end of the turn or ask the UA to schedule
>> >> a future task.
>> >>
>> >> Referring back to my reason (1) This question was debated extensively
>> >> with DOM Mutation Observers and unanimously decided that the end of
>> >> the turn was the only good solution. "immediately" puts all code in
>> >> danger of having its runtime assumptions invalidated after every
>> >> operation which mutates objects and "future task" is far too late to
>> >> be useful for most almost all use cases.
>> >
>> >
>> >
>> > I can understand how batched changes and end-of-turn asynchronous calls
>> > are
>> > a good match.  The development model is now "here are the changes from
>> > this
>> > turn" rather than "here is a change".
>> >
>> > But I don't understand how end-of-turn asynchronous calls helps with the
>> > multiple independent listeners problem. If a listener can mutate the
>> > DOM,
>> > then change-listeners depend upon change-listeners. The relative timing
>> > of
>> > the listener calls and the non-listener DOM mutations does not change
>> > that
>> > dependency.
>> >
>> > And once change-listeners mutate the DOM then the API of "here are the
>> > changes from this turn" again misleads the developer relying on it.
>>
>> Actually, this isn't quite right. It's "here are all the changes since
>> the last time you were invoked". I.e. every observer is always
>> delivered all changes to all entities it is observing up to the time
>> that it is invoked. To be concrete, the following invariants always
>> hold:
>>
>> A) When an observer is invoked, is it delivered a sequence of change
>> records representing all changes to entities it is observing -- up to
>> the time of its invocation.
>> B) When an observer is invoked, it is free to mutate entities and do
>> work without risk of being preempted by other observers being notified
>> of changes it makes.
>> C) Conversely, when an observer is invoked, no other script is below
>> it on the stack.
>> D) An observer will be delivered all changes to entities it is
>> observing before the turn ends.
>>
>> These are true for both Object.observe() and for DOM Mutation Observers.
>>
>> In other words, any given observer can be blissfully ignorant of all
>> other actors in the system. It is always delivered a full picture of
>> what has happened in its world, it can act completely independently of
>> other actors, and it will always get all of its changes before the
>> turn ends.
>>
>> Does this address your concern?
>
>
> Can we create a simple example that demonstrates the API advantage of
> asynchronous events in the multiple observer case?
>
> The example would need a DOM mutations (M1) and two handlers (H1 and H2) and
> let's have H1 mutate the DOM (M2) in a way that H2 depends upon

If:
-I understand your notation
-We use DOM Mutation Event's behavior as the model of synchronous
-DOM Mutation Observers and/or Object.observe() as the model of async,

..then you have it basically right, but it's actually worse in the
sync case and better in the async case than you've stated, as:

-In the H1-first case, H2 gets to handle both M1 and M2 in the same
invocation, as those represent the changes up to that point
-In the both cases, H1 gets notified of its own changes because in the
sync model there's no good way to ignore your own changes. In the
async model, it's trivial -- you just suspend observation while you do
your work.

>
> Synchronous, H1 first:
>    H1(M1) H2(M2) H2(M1)  <<< correct? or H2(M1, M2)?

H1(M1) H1(M2) H2(M2) H2(M1) <<< yes, but H1 has to tolerate being
notified of his own change.

> Async, H1 first
>    H1(M1) H2(M1)  H2(M2)

H1(M1) H2(M1, M2)

>
> Sync H2 first:
>    H2(M1) H1(M1) H2(M2)

H2(M1) H1(M1) H1(M2) H2(M2)

> Async
>   H2(M1) H1(M1) H2(M2)

correct

>
> If I am understanding correctly (and did the example correctly ;-), then the
> async model fires all handlers with 1st order DOM mutations, then fires all
> handlers with mutations created by other handlers (possibly repeating).
>
> In the sync model, 2nd order mutations-from-mutation-handlers have to be
> delivered before we finish the first round.
>
> If I'm understanding correctly then the async model is clearer.
>
> jjb
>


More information about the es-discuss mailing list