dean edwards_ callbacks

Upload: venkatesh-babu

Post on 09-Apr-2018

216 views

Category:

Documents


0 download

TRANSCRIPT

  • 8/8/2019 Dean Edwards_ Callbacks

    1/10

    Callbacks vs Events

    Most of the major JavaScript libraries claim to support custom events in one form oranother. For example, jQuery, YUI and Dojo all support a custom document readyevent. However, the implementation of these custom events is always some form ofcallback system.

    A callback system works by storing event handlers in an array. When the actualevent is detected the dispatch system loops through the array calling the callbackfunctions in turn. So whats wrong with that? Before I answer that, lets look at somecode.

    Here is some simple code that uses the DOMContentLoaded event to perform twoseparate initialisations:

    document.addEventListener("DOMContentLoaded", function() {console.log("Init: 1");

    DOES_NOT_EXIST++; // this will throw an error}, false);

    document.addEventListener("DOMContentLoaded", function() {console.log("Init: 2");

    }, false);

    What do you expect to see in the console when the document is loaded?

    Well, you should see this (or something like it):

    Init: 1

    Error: DOES_NOT_EXIST is not defined

    Init: 2

    The point is, both functions are executed. You get an error in the first function but itdoes not stop the second function from executing.

    The Problem

    Now lets look at some code based on a callback system. Ill pick on jQuery becauseits the most popular:

    $(document).ready(function() {console.log("Init: 1");DOES_NOT_EXIST++; // this will throw an error

    });

    $(document).ready(function() {console.log("Init: 2");

    });

    What do we see in the console?

    Init: 1

    Error: DOES_NOT_EXIST is not defined

    The problem is clear. Callback systems are brittle. If any of the callback functionsthrows an error then the subsequent callbacks are not executed. In reality, this

    n Edwards: Callbacks vs Events http://deanedwards.me.uk/weblog/2009/03/callbacks-vs-events/

    10 3/31/2009 11:40 PM

  • 8/8/2019 Dean Edwards_ Callbacks

    2/10

  • 8/8/2019 Dean Edwards_ Callbacks

    3/10

    };

    Now well use the code above to attach our two troublesome event handlers:

    addOnLoad(function() {console.log("Init: 1");DOES_NOT_EXIST++; // this will throw an error

    });

    addOnLoad(function() {console.log("Init: 2");});

    OK. Lets run the code above and see what we get:

    Init: 1

    Error: DOES_NOT_EXIST is not defined

    Init: 2

    Perfect! Just what we want. Both event handlers are executed and we also get a

    message telling us about the error in the first handler. Great!

    But what about Internet Explorer I hear you ask (I have good hearing). MSIE doesnot support the standard event dispatch system. It has its own method; fireEventbut that only works with real events (e.g. click).

    Rather than explain the solution in words, here is the code:

    var currentHandler;

    if (document.addEventListener) {

    // We've seen this code already

    } else if (document.attachEvent) { // MSIE

    document.documentElement.fakeEvents = 0; // an expando property

    document.documentElement.attachEvent("onpropertychange", function(event) {if (event.propertyName == "fakeEvents") {

    // execute the callbackcurrentHandler();

    }});

    dispatchFakeEvent = function(handler) {// fire the propertychange eventdocument.documentElement.fakeEvents++;

    };}

    A similar approach except that we use the proprietary propertychange event as thetrigger.

    Summary

    Ive shown a very simple example of how to use the uderlying event system to firecustom events. Library authors should be capable of seeing how this can beextended to fully support cross-browser custom events.

    Update

    n Edwards: Callbacks vs Events http://deanedwards.me.uk/weblog/2009/03/callbacks-vs-events/

    10 3/31/2009 11:40 PM

  • 8/8/2019 Dean Edwards_ Callbacks

    4/10

    Some commenters have suggested using setTimeout. Here is my response to that:

    For this particular example, a timer will work fine. This is just an exampleto illustrate the technique. The real usefulness of this is for other customevents. Most libraries implement custom events using a callback system.As I illustrated, callback systems are brittle. Dispatching events with timerswill work to a degree but it is not how a real event system works. In a real

    system, events are dispatched sequentially. There are other concerns, likecancelling an event and stopping the event from bubbling. This would beimpossible with timers.

    The important thing is that Ive demonstrated a technique for wrapping a callbacksystem in a real event dispatch system. That gives you the ability to fire realcustomevents in MSIE. If you are building an event system based on event delegation thenthis technique may also be interesting to you.

    Update 2

    It seems that Prototype uses an almost identical trick to fire custom events in MSIE:

    http://andrewdupont.net/2009/03/24/link-dean-edwards/ (link:http://andrewdupont.net/2009/03/24/link-dean-edwards/)

    Leave a comment

    Comments (36)

    Great to see you blogging again Dean, a nice article on a subject I never

    completely understood. But time to ask the obvious to you as a library author:When will you implement this in base2?

    Comment by: Marijn HuizendveldPosted: 2009/03/24 7:37 pm

    1.

    Marijn, I recently implemented this in base2 but I mostly developed it for JSB, abehaviors library Im building on top of base2. I have a bunch of new code torelease soon. You will see previews on the base2 mailing list within a week ortwo.

    I intend to blog a lot more this year too.

    Comment by: -dean (link: http://dean.edwards.name/)Posted: 2009/03/24 7:47 pm

    2.

    I experimented a little with the JSB implementation but I was wondering if youwere going to make it base2 native code.. Anyway it looks really nice and Imlooking forward to more blogposts and new code releases

    Comment by: Marijn HuizendveldPosted: 2009/03/24 8:00 pm

    3.

    Yep, Im glad to see you blogging again as well! It never occurred to me thatlibraries implemented this so poorly, interesting to see your solution!

    Comment by: Marc

    4.

    n Edwards: Callbacks vs Events http://deanedwards.me.uk/weblog/2009/03/callbacks-vs-events/

    10 3/31/2009 11:40 PM

  • 8/8/2019 Dean Edwards_ Callbacks

    5/10

    Posted: 2009/03/24 8:00 pm

    Why not dispatch the handlers with setTimeout? That would be much simplerBut you probably see something which I am missing

    Comment by: Doeke Zanstra (link: http://zanstra.com/base/blog)Posted: 2009/03/24 8:12 pm

    5.

    @Marijn, you can consider it base2 native code. base2 uses the standarddispatchEvent mechanism for custom events. This technique is used to enable itfor MSIE.

    Comment by: -dean (link: http://dean.edwards.name/)Posted: 2009/03/24 8:16 pm

    6.

    @Doeke, If I used setTimeout then the event is dead by the time the handlergets it. That means that you cant cancel the event using preventDefault. Also,events occur sequentially. setTimeout would completely break that.

    Although, for this example, setTimeout would work just as well.

    If you want to fully support cross-browser custom events then you need adispatch mechanism for MSIE. This is the only way I can think of dong it.

    Comment by: -dean (link: http://dean.edwards.name/)Posted: 2009/03/24 8:18 pm

    7.

    @Doeke Zanstra:+1

    I was just about to mention this. In my library projects which utilizeuser-supplied callbacks, I use timeout(func, 0). For example:

    try {callback();

    }catch(e){

    setTimeout(function(){throw e;

    }, 0);}

    This prevents the execution from aborting because of a bad callback, and it is

    cross-browser. Is there something less favorable about this approach? Thecallbacks are occurring sequentially, but the errors are reported to the browserasynchronously.

    Comment by: Weston Ruter (link: http://weston.ruter.net/)Posted: 2009/03/24 8:21 pm

    8.

    [...] Dean Edwards explains how the standard callback pattern in JavaScript istoo brittle for something like a DOM ready event. Prototype appears to be theone major library that handles this correctly. WIN! [...]

    Pingback by: Painfully Obvious Blog Archive Link: Dean Edwards (link:

    http://andrewdupont.net/2009/03/24/link-dean-edwards/)

    Posted: 2009/03/24 8:25 pm

    9.

    Weston, you posted your comment as I was responding to Doeke. For thisparticular example, a timer will work fine. This is just an example to illustratethe technique. The real usefulness of this is for other custom events. Most

    10.

    n Edwards: Callbacks vs Events http://deanedwards.me.uk/weblog/2009/03/callbacks-vs-events/

    10 3/31/2009 11:40 PM

  • 8/8/2019 Dean Edwards_ Callbacks

    6/10

    libraries implement custom events using a callback system. As I illustrated,callback systems are brittle. Dispatching events with timers will work to adegree but it is not how a real event system works. In a real system, events aredispatched sequentially. There are other concerns, like cancelling an event andstopping the event from bubbling. This would be impossible with timers.

    Comment by: -dean (link: http://dean.edwards.name/)Posted: 2009/03/24 8:26 pm

    Whoa, youre blogging again!

    Comment by: IlyaPosted: 2009/03/24 8:28 pm

    11.

    @Dean: I forgot to mention this, but Im glad you started blogging again. I hadseen the event dispatching code (especially the onpropertychange), but didntquite grasp it. It becomes much clearer now; Im learning so much again

    Comment by: Doeke Zanstra

    Posted: 2009/03/24 8:35 pm

    12.

    @Dean, fun writeup I like the concept. So Dojo doesnt have custom eventsin this sense (though Id like it to) but we have pub/sub, which as you are likelywell aware is just function connections. I wrote a quick thing to makedojo.addOnLoad behave in this sense using the topic api already in place:

    var d = dojo, oal = d.addOnLoad;d.addOnLoad = function(fn){ // doesn't handle scope like addOnLoad does

    d.subscribe("/window/onload", function(){try{ fn(); }catch(e){ console.warn(e); }

    });

    }oal(d.partial(d.publish, "/window/onload"));

    just thinking out loud. Nothing to see here.

    Regards, Peter

    Comment by: Peter Higgins (link: http://higginsforpresident.net)Posted: 2009/03/24 9:40 pm

    13.

    @Peter, I always liked Dojos pub/sub functionality. And youre just showing offwith that partial application.

    Comment by: -dean (link: http://dean.edwards.name/)Posted: 2009/03/24 9:48 pm

    14.

    @dean - I found a bug I forgot to disconnect. We do the lazy-loading, soonLoad will fire every time. var s = dojo.subscribe(function(){dojo.unsubscribe(s); })

    and I wasnt trying to show off anything but how fun JavaScript is with thepartial call, I promise. What a wonderful language it is, eh? hitch/partial/curry/bind/whateveryoucallthatpattern should part of the spec.

    Comment by: Peter Higgins (link: http://higginsforpresident.net)Posted: 2009/03/24 9:57 pm

    15.

    Another approach is to buffer possible exceptions until all callbacks have been16.

    n Edwards: Callbacks vs Events http://deanedwards.me.uk/weblog/2009/03/callbacks-vs-events/

    10 3/31/2009 11:40 PM

  • 8/8/2019 Dean Edwards_ Callbacks

    7/10

    visited. Mochikit does this for example.

    Comment by: FredrikPosted: 2009/03/24 10:52 pm

    @Fredrik, I prefer my JavaScript to break where the actual error is. If you bufferexceptions then it is pretty hard to debug. How do you inspect the stack if theerror has already passed?

    Comment by: -dean (link: http://dean.edwards.name/)Posted: 2009/03/24 11:06 pm

    17.

    @Peter: Function.prototype.bind is part of ES 3.1 and lets you do partialapplication, so it looks like your wishes will be granted.

    Comment by: Tobie Langel (link: http://tobielangel.com)Posted: 2009/03/24 11:26 pm

    18.

    Dean, great and simple explanation on how to profit this useful behavior of

    events.

    For me no doubt an event is generally better then a timer. The properties youmay read out from the system objects in a timed event may be different fromthose read out during an event notification.

    Now we have to learn how to profit the asynchronous aspects of it and avoid itspitfalls too. I imagine we can now have a better use of throw

    Have you thought about the necessity to handle failures of previous callbacksfrom successive callbacks (plugins depending on others) ?

    Comment by: Diego Perini (link: http://www.iport.it)Posted: 2009/03/25 2:20 am

    19.

    Have you thought about the necessity to handle failures of previouscallbacks from successive callbacks (plugins depending on others) ?

    I cant say that I have. Im trying to solve one problem here. Giving aseparate execution context to each event handler. Thats it.

    Comment by: -dean (link: http://dean.edwards.name/)Posted: 2009/03/25 2:57 am

    20.

    FYI, Ive never used this code, but heres Mochikits callback routine (link:http://trac.mochikit.com/browser/mochikit/tags/MochiKit-1.4.2/MochiKit/Signal.js#L821) . Apparently, 1 error = its rethrown. > 1 error = A new errorthrown with an errors property containing all collected errors.

    Comment by: Steve Clay (link: http://mrclay.org/)Posted: 2009/03/25 4:58 am

    21.

    Dojo unifies events into after-advice on function calls. We treat DOM events thisway and we treat regular function application just the same. Its all unified bydojo.connect(), therefore the idiomatic way to do this in Dojo is:

    // listen on the dojo.loaded() functiondojo.connect(dojo, "loaded", function(){ ... });

    22.

    n Edwards: Callbacks vs Events http://deanedwards.me.uk/weblog/2009/03/callbacks-vs-events/

    10 3/31/2009 11:40 PM

  • 8/8/2019 Dean Edwards_ Callbacks

    8/10

    Regards

    Comment by: Alex Russell (link: http://alex.dojotoolkit.org)Posted: 2009/03/25 5:02 am

    hmm.. very clever

    I believe and I think its worth noting too.. that collecting callbacks via native

    addEventListener and then firing them using dispatchEvent is more efficient thancollecting it to JavaScript array and iterating over it. However I havent tested it.

    I wonder if its possible to make some use of third argument (capture/bubblephase) in custom events.. will it accept just true/false or maybe wider set ofpossibilities.

    Comment by: medikoo (link: http://www.medikoo.com)Posted: 2009/03/25 2:51 pm

    23.

    @dean, comment form doesnt accept + in email address.. is it bug or a

    feature ?

    Comment by: medikoo (link: http://www.medikoo.com)Posted: 2009/03/25 2:53 pm

    24.

    Rather than have a global currentHandler variable, its a better idea to pass thecallback as a parameter to the function youre invoking. This allows you to dothings like arbitrarily nest sequences without conflicts.

    I did a write-up on a technique I developed for this a year or so ago. You canfind it at, http://blog.mendix.com/?p=4 (link: http://blog.mendix.com/?p=4) .

    Basically I use a sequencer function that passes a reference to an anonymousinner function to a function I shift off the array (which is a function that acceptsa callback as its only parameter and invokes it on completion).

    Comment by: Michiel KalkmanPosted: 2009/03/25 6:37 pm

    25.

    @Michiel, yes this isnt the best way to write the code but its the easiest tounderstand. I always write my example code in the simplest way possible. Agood programmer can always recode it to their tastes.

    Comment by: -dean (link: http://dean.edwards.name/)Posted: 2009/03/25 7:45 pm

    26.

    Good post. The other reason I like using events over call backs at times is forunit testability. I then find that with jQuery plugin code for example, unit testingthem helps drive out the events that should be fired, creating a richer plugin asa result. Probably easiest way to explain is to point to a post about that I wrotea little while back: http://www.onenaught.com/posts/85/turn-your-jquery-code-into-a-richer-unit-testable-plugin (link: http://www.onenaught.com/posts/85/turn-your-jquery-code-into-a-richer-unit-testable-plugin)

    Comment by: Anup (link: http://www.onenaught.com)Posted: 2009/03/25 7:47 pm

    27.

    Im actually knee-deep in this issue for a soon-to-be-released framework-28.

    n Edwards: Callbacks vs Events http://deanedwards.me.uk/weblog/2009/03/callbacks-vs-events/

    10 3/31/2009 11:40 PM

  • 8/8/2019 Dean Edwards_ Callbacks

    9/10

    agnostic unit test library Im working on. As Im trying to support as manyplatforms as possible, using custom events really wasnt a option. Turns outsome platforms also dont have setTimeout (Opera mini, for example), so Iended up using recursion and a try..catch..finally block:

    function iterate(callbacks, length, i) {if (i >= length) { return; }try {

    callbacks[i]();} catch(e) {throw e;

    } finally {iterate(callbacks, length, i+1);

    }}

    // clone callbacks array in case a callback is// removed during iterationvar c = Array.prototype.slice.call(callbacks, 0);

    iterate(c, c.length, 0);

    In firebug:

    var a = [function() { console.log(0); },function() { console.log(1); throw new Error; },function() { console.log(2); },function() { console.log(3); }

    ];

    iterate(a, a.length, 0);// -> 0// -> 1// -> 2

    // -> 3// -> Error

    Any know issues with this solution ?

    Comment by: Tobie Langel (link: http://tobielangel.com)Posted: 2009/03/27 6:40 am

    Well thats what happens when you work too late. The above obviously onlythrows the first error and stops further processing. Please just ignore.

    Comment by: Tobie Langel (link: http://tobielangel.com)Posted: 2009/03/27 3:54 pm

    29.

    [...] Dean Edwards: Callbacks vs Events Ive shown a very simple example ofhow to use the uderlying event system to fire custom events. Library authorsshould be capable of seeing how this can be extended to fully support cross-browser custom events. (tags: javascript events callbakcs howto webdev) [...]

    Pingback by: links for 2009-03-27 (link: http://www.mingli-

    yuan.info/archives/351)Posted: 2009/03/27 6:04 pm

    30.

    [...] Dean Edwards talking about Callbacks vs. Events [...]Pingback by: Wait till I come! Blog Archive TTMMHTM: Reflows, real tetris, enablingdesign, swf checking and micnosing (link: http://www.wait-till-i.com/2009/03/28/ttmmhtm-reflows-real-tetris-enabling-design-swf-checking-and-micnosing/)Posted: 2009/03/28 7:32 pm

    31.

    [...] Callbacks vs Events jQuery [...]Pingback by: Linkdump #12 | CTAPbIu_MABP's BLOG (link: http://mabp.kiev.ua/2009/03/29/linkdump-12/)

    32.

    n Edwards: Callbacks vs Events http://deanedwards.me.uk/weblog/2009/03/callbacks-vs-events/

    10 3/31/2009 11:40 PM

  • 8/8/2019 Dean Edwards_ Callbacks

    10/10

    Posted: 2009/03/29 4:42 pm

    Good to see you today Dean! I was excited when I saw your name pop up inGoogle Reader, and you didnt disappoint.

    Comment by: brad dunbar (link: http://beedii.com)Posted: 2009/03/30 1:48 pm

    33.

    Good solution. I have used the try/catch approach recently in one of myprojects and collect all errors in an Array along with the function name in whichthey occured. Its really only a small init file, so it works fine. Will consider yourapproach on one of the next refactoring sessions.

    Comment by: GPosted: 2009/03/30 10:05 pm

    34.

    [...] Edwards has a subtle piece on callbacks vs. events where he calls out theissue of libraries that support custom events dying out when one custom [...]

    Pingback by: Ajaxian The differences between Callbacks and Events (link:http://ajaxian.com/archives/the-differences-between-callbacks-and-events)

    Posted: 2009/03/31 12:04 pm

    35.

    [...] http://deanedwards.me.uk/weblog/2009/03/callbacks-vs-events/ (link:http://deanedwards.me.uk/weblog/2009/03/callbacks-vs-events/) : un articlede Dean Edwards sur la diffrence entre callbacks et events [...]

    Pingback by: Dev Blog AF83 Blog Archive Veille technologique : A la une, Mthodes,Outils, Javascript, PHP, MySQL, Performances, Wallpapers (link: http://dev.af83.com/veille-technologique/veille-technologique-a-la-une-methodes-outils-javascript-php-mysql-performances-wallpapers/2009/03/31/)Posted: 2009/03/31 4:14 pm

    36.

    Line and paragraph breaks automatic, email address never displayed.

    n Edwards: Callbacks vs Events http://deanedwards.me.uk/weblog/2009/03/callbacks-vs-events/