- From: Jonas Sicking <jonas@sicking.cc>
- Date: Thu, 15 Dec 2011 22:53:17 -0800
- To: Ojan Vafai <ojan@chromium.org>
- Cc: Ian Hickson <ian@hixie.ch>, Alex Russell <slightlyoff@google.com>, Cameron McCormack <cam@mcc.id.au>, "public-script-coord@w3.org" <public-script-coord@w3.org>, Anne van Kesteren <annevk@opera.com>
On Thu, Dec 15, 2011 at 1:11 PM, Ojan Vafai <ojan@chromium.org> wrote: > On Thu, Dec 15, 2011 at 12:49 PM, Jonas Sicking <jonas@sicking.cc> wrote: >> >> On Thu, Dec 15, 2011 at 11:47 AM, Ojan Vafai <ojan@chromium.org> wrote: >> > On Wed, Dec 14, 2011 at 9:15 PM, Jonas Sicking <jonas@sicking.cc> wrote: >> >> >> >> On Wed, Dec 14, 2011 at 4:35 PM, Ian Hickson <ian@hixie.ch> wrote: >> >> > On Wed, 14 Dec 2011, Cameron McCormack wrote: >> >> >> >> >> >> Either we embrace object-with-property for all APIs that take >> >> >> callbacks, >> >> >> giving them meaningful method names, or we decide that all future >> >> >> APIs >> >> >> take only Functions. Allowing object-with-property for new APIs but >> >> >> using handleEvent for them all seems like a sucky compromise to me. >> >> > >> >> > I think that the sucky situation would be to have lots of places that >> >> > take >> >> > callbacks, but have to keep looking up what the heck the function is >> >> > called in order to use the object form. >> >> > >> >> > Consider an API that you can use whenever a callback is needed to >> >> > track >> >> > the number of calls to the callback. If every callback uses a >> >> > different >> >> > function name, you'd have to tell this API what the context of the >> >> > callback was instead of just being able to use it blindly. Consider >> >> > what >> >> > it would take to change the implementation of such an API from using >> >> > closures to using an object to store data. Suddenly, every call site >> >> > would >> >> > need to be updated to provide the callback method name. >> >> > >> >> > Don't think of "handleEvent" as meaning "handle a DOM Event object". >> >> > Think >> >> > of it as "handle an event", the event being "the callback was >> >> > invoked". >> >> > There's plenty of precedent for callbacks being called "HandleEvent". >> >> >> >> We have at least 3 options here: >> >> >> >> 1. Accept only Functions (except where webcompatibility requires >> >> otherwise) >> >> 2. Accept Functions and objects with a handleEvent function >> >> 3. Accept Functions and objects with a descriptive function name. >> >> >> >> If the callback has the same name everywhere then it adds absolutely >> >> no value over simply accepting only functions. People would end up >> >> having to write code like >> >> >> >> x = { >> >> handleEvent: function(args) { >> >> if (... detect first callback type based on args ...) { >> >> doStuff(); >> >> } >> >> if (... detect second callback type based on args ...) { >> >> doOtherStuff(); >> >> } >> >> etc; >> >> }, >> >> doStuff: function() {...}, >> >> doOtherStuff: function() {...}, >> >> moreProps: "here" >> >> } >> >> >> >> registerCallbackFunc(x); >> >> registerOtherCallbackFunc(x); >> >> >> >> >> >> Compare this to: >> >> >> >> x = { >> >> doStuff: function() {...}, >> >> doOtherStuff: function() {...}, >> >> moreProps: "here" >> >> } >> >> >> >> registerCallbackFunc(function() { x.doStuff() }); >> >> registerOtherCallbackFunc(function() { x.doOtherStuff() }); >> >> >> >> The latter is much cleaner and understandable. Not to mention that the >> >> first pattern doesn't work at all if you can't tell the types of >> >> callbacks apart based on what arguments they are given. >> >> >> >> So for the sake of not introducing useless features, I would say that >> >> 1 is strictly better than 2. That way people can use Function.bind and >> >> direct callbacks directly to where they want. >> >> >> >> >> >> However I think 3 has value in that it allows you to create a single >> >> object which listens to callbacks from many different APIs. >> >> >> >> x = { >> >> onCallbackFunc: function() {...}, >> >> onOtherCallbackFunc: function() {...}, >> >> moreProps: "here" >> >> }; >> >> >> >> registerCallbackFunc(x); >> >> registerOtherCallbackFunc(x); >> >> >> >> >> >> I don't think that having to memorize the callback names will be any >> >> worse having to remember any other names in the DOM. I think the DOM >> >> would be an even worse API if we renamed all functions to "function" >> >> and all the properties to "property". >> > >> > >> > I don't see the value in option 3. I have yet to see an example where >> > option >> > 3 gives you something meaningfully better than just passing a function. >> > It's >> > complexity without significant benefits. >> >> The UndoManager is a great example of where this is done in a >> meaningfully better way than can be done with functions. > > > That's not the same thing. The UndoManager never takes a callback. It > explicitly only takes an object and it makes sense in that case since it > needs to support multiple callbacks. The equivalent would be an API that > takes multiple callbacks. The whole point of 3 is to enable people to write objects that listens to multiple callbacks. That is the sole reason for wanting different names. Just because addEventListener doesn't itself call more than one function name, doesn't mean that the observer is only listening to callbacks from addEventListener. So one object could be registering to get callbacks both from HTMLCanvasElement.toBlob (if it supported objects) and addEventListener. People have made the argument that people can simply capture any state using a closure and that way avoid using objects completely. But if you look at what big javascript codebases look like you'll see that they use objects with member functions all over the place. People seem to like to use an object oriented approach when writing their code. Saying that people don't need to use objects is certainly true, but also ignores the giant movement towards object oriented programming style over the past decade(s?). This is why I think option 1 is worse than option 3, it ignores the programming style that people are actually using in the wild. The main problem with using objects for callbacks today is that addEventListener is by far the most common way to get callbacks, and it always uses "handleEvent". Hopefully we can introduce better API on EventTarget which doesn't replicate this problem (Anne made some suggestions for a better EventTarget API recently, we can work this fix into that proposal). But at least addEventListener uses consistent arguments. You know you are always getting an Event and can check its .type to see where to dispatch. If you were Listening for callbacks from both addEventListener and Geolocation.getCurrentPosition is a real pain since both call handleEvent but with different argument types. So you have to use 'instanceof' to tell what you are being called back from. A cleaner solution would likely to be to not use object callbacks but rather functions with closures. This is why the 2 option is strictly sub-par to the 1 option, it just introduces a useless feature. Hence I think we should use option 3. / Jonas
Received on Friday, 16 December 2011 06:54:17 UTC