Re: [WebIDL] Simplify callbacks

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