Rafael Weinstein (2014-02-16T15:50:38.000Z)
domenic at domenicdenicola.com (2014-02-20T17:33:52.251Z)
On Sun, Feb 16, 2014 at 5:25 AM, Woodlock, Don (GE Healthcare) <Don.Woodlock at med.ge.com> wrote: > I have some feedback into the Object.observe proposal. > Now I may be missing something so any clarification or education would be > appreciated as well. But I do love this particular spec and can’t wait to > see it implemented. But while I was implementing a pseudo-polyfill of it > for my own purposes, I noticed an ‘information hole’ in the ChangeRecords > design. This is due to the asynchronous nature of the notification > delivery and that subsequent mutations may have occurred by the time the > notification is ‘delivered’. In particular I believe a new ‘added’ > property is needed to the ChangeRecords of the splice transaction of an > array mutation in the Array.observe design. > > Here’s the particular situation: > > First of all I’m assuming that the purpose of the > ChangeRecords is to give the ChangeObserver enough information, beyond the > fact that a change occurred, so that it doesn’t have to reprocess > everything, but can be specific and efficient based on what in particular > has changed. For example if I have a view that lists a bunch of students > and it’s observing an array of students, if a student gets added to the > array, I’d like to know enough so the view can just add the one student and > doesn’t have to redisplay the entire list because it knows the array has > changed somehow. Secondly I’ll assuming that the Chrome Canary > implementation of Array.observe is suitably similar to the spec as that’s > where I have been learning about what this design looks like in practice. > > Let’s say you have the array: [“c”, “d”, “f”]. If you are > observing the array, and this operation occurs myArray.unshift(“b”), you > will get the following notification: > > ```js > type = “splice”, > removed = [], > object: pointer to the array > index: 0 > addedCount: 1 > ``` > > You would naturally determine what was > added via `changeRecord.object.slice(changeRecord.index, changeRecord.index + changeRecord.addedCount);` That would show you that “b” got added. > > Subsequently (and with a delay), if you did > `myArray.unshift(“a”);`, you will get an identically looking notification, > and through the same approach, you would see that “a” got added. You can > see this if you run the first attachment in Chrome Canary. If you run > this, you can see in the console log that the fact that the changeObserver > determines ‘b’ and then ‘a’ were added appropriately. > > The problem exists if I don’t put a delay between the two > unshift operations. If you look at the second attachment, I remove the > delay so that myArray.unshift(“b”) and then myArray.unshift(“a”) happen one > after another. The first notification goes out to the ChangeObserver > asynchronously and after the second mutation occurs. So when processing > that first notification, using the approach above, I use the index and > addedCount against the current version of the array (after both mutations) > and I determine that “a” was added when it was really “b” because “a” is > now in index 0. After I process the second notification, I also think that > “a” got added. You’ll see this if you run the second attachment in > Canary. So I completely miss that “b” was ever added and could also > reasonably assume that “a” was added again. > > So that’s the problem, as an observer you would like > enough information in the ChangeRecords to process only what has changed, > but you would essentially miss that “b” was added to the array because that > information is not supplied. In this design, you need to infer what was > added (vs. what was removed which is specified), by looking at the current > object, but again subsequent mutations could have occurred so some changes > are obscured in this design and you would be forced to reprocess everything > – which is not the point of ChangeRecords. > > So my recommendation is that a data property named “added” > to added to the ChangeRecord of a splice transaction to show the array > elements that were added during the mutation that led to that particular > ChangeRecord. You may have been trying in this design to not pass > information that is current and rather have the user reference the array > directly. But the ‘added’ property is not necessary repeating current > information because of the subsequent mutations. It would be a useful > glimpse of the past similar to the removed property. > > Object.observe has a similar problem but less problematic > in practice. If you change a property on an object being observed to a > different value and then change it back, it would not be possible for the > observer of the first notification to see what the property was changed > to. A ‘newValue’ property, for the same reasons as above, would be a > useful addition even though in many cases, not all, it is the same as could > be ascertained by looking at the current object directly. > > Again I may be confused so any education would be > appreciated. But it appears that this is a big information hole that would > cause ChangeObservers to miss when elements get added to arrays which > defeats the point of these ChangeRecords. I hope this was clear and > useful. Any questions or clarifications on this explanation, let me know. > Thx. Thanks for the thoughtful feedback. I'm very glad you're excited about the feature. The short answer to your question is: the data you want is provided, but your processing needs to be more sophisticated. [BTW, The Chrome/V8 implementation (to the best of my knowledge) fully implements the latest spec for Object.observe] Basically, you're making an assumption about processing log data which is understandable, but unfortunately false. Though it would be nice, it's simply not possible to process each change record by looking at the final state of the observed object. Object.observe provides full information about the changes that occur, but depending on what view you want, you may need to do some processing to get the right answer. Based on the use-case you describe, it sounds like you have a very common desire -- to synchronize two objects, one of which has changed. The pattern requires what I'll call a "diff in time" view of the changes. In other words: from time 0 to time 1, what's the minimal set of changes I would need to apply to a copy of the observed object at time 0 in order to transform it into a copy of object at time 1? As you've found, Object.observe doesn't provide you this view of things. It provides you with a log of what happened along the way. However, you can use the log view as input into a transform which will provide you with the diff view. I've provided a JS library which serves multiple purposes: -A handy library for those who want the "diff" view of things -A reference implementation of some of these algorithms -A polyfill so that you may use this functionality in browsers that don't implement Object.observe (the polyfil mode has less desirable performance -- specifically the cost to determine what has changed is proportional to the total set of observed objects -- as opposed to Object.observe where the cost is proportional to the number of changes which took place). The library is here: https://github.com/Polymer/observe-js It may be sufficient for your use case (as well as act as the polyfill you were attempting to create) or it may simply serve as documentation for the processing that is required. Thanks again for your feedback and have fun.