Callbacks vs Events

Most of the major JavaScript libraries claim to support custom events in one form or another.
For example, jQuery, YUI and Dojo all support a custom “document ready” event. However, the implementation
of these custom events is always some form of callback system.

A callback system works by storing event handlers in an array. When the underlying event is detected the dispatch system loops through the array calling the callback functions in turn. So what’s wrong with that? Before I answer that, let’s look at some code.

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

The problem is clear. Callback systems are brittle. If any of the callback functions throw
an error then the subsequent callbacks are not executed. In reality, this means that a poorly written
plugin can prevent other plugins from initialising.

Dojo suffers exactly the same problem as jQuery. The YUI library takes a slightly different approach.
It wraps a try/catch around its dispatch mechanism. The downside is that your errors occur
silently:

Perfect initialisation! Nothing to worry about here! Except for the error that you don’t see.

So what’s the solution?

The Solution

The solution is to use a hybrid of a callback system and real event dispatch.
We can trigger a fake event and from within that event, run the callback function.
Each event handler has its own execution context. If an error occurs in our fake event
then it won’t affect our callback system.

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 does not support
the standard event dispatch system. It has its own method; fireEvent but that only
works with real events (e.g. click).

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

Summary

I’ve shown a very simple example of how to use the uderlying event system to fire custom
events. Library authors should be capable of seeing how this can be extended to fully support
cross-browser custom events.

Update

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 example to illustrate the technique. The real usefulness of this is for other custom events. Most libraries implement custom events using a callback system. As I illustrated, callback systems are brittle. Dispatching events with timers will 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, like cancelling an event and stopping the event from bubbling. This would be impossible with timers.

The important thing is that I’ve demonstrated a technique for wrapping a callback system in a real event dispatch system. That gives you the ability to fire real custom events in MSIE. If you are building an event system based on event delegation then this technique may also be interesting to you.

Update 2

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

Marijn, I recently implemented this in base2 but I mostly developed it for JSB, a behaviors library I’m building on top of base2. I have a bunch of new code to release soon. You will see previews on the base2 mailing list within a week or two.

I experimented a little with the JSB implementation but I was wondering if you were going to make it base2 “native” code.. Anyway it looks really nice and I’m looking forward to more blogposts and new code releases

@Doeke, If I used setTimeout then the event is “dead” by the time the handler gets it. That means that you can’t 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 a dispatch mechanism for MSIE. This is the only way I can think of dong it.

This prevents the execution from aborting because of a bad callback, and it is cross-browser. Is there something less favorable about this approach? The callbacks are occurring sequentially, but the errors are reported to the browser asynchronously.

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

Weston, you posted your comment as I was responding to Doeke. For this particular example, a timer will work fine. This is just an example to illustrate the technique. The real usefulness of this is for other custom events. Most libraries implement custom events using a callback system. As I illustrated, callback systems are brittle. Dispatching events with timers will 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, like cancelling an event and stopping the event from bubbling. This would be impossible with timers.

@Dean: I forgot to mention this, but I’m glad you started blogging again. I had seen the event dispatching code (especially the onpropertychange), but didn’t quite grasp it. It becomes much clearer now; I’m learning so much again

@Dean, fun writeup — I like the concept. So Dojo doesn’t have ‘custom events’ in this sense (though I’d like it to) but we have pub/sub, which as you are likely well aware is just function connections. I wrote a quick thing to make dojo.addOnLoad “behave” in this sense using the topic api already in place:

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

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

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

I believe and I think it’s worth noting too.. that collecting callbacks via native addEventListener and then firing them using dispatchEvent is more efficient than collecting it to JavaScript array and iterating over it.
However I haven’t tested it.

I wonder if it’s possible to make some use of third argument (capture/bubble phase) in custom events.. will it accept just true/false or maybe wider set of possibilities.

Rather than have a global currentHandler variable, it’s a better idea to pass the callback as a parameter to the function you’re invoking. This allows you to do things like arbitrarily nest sequences without conflicts.

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

@Michiel, yes this isn’t the best way to write the code but it’s the easiest to understand. I always write my example code in the simplest way possible. A good programmer can always recode it to their tastes.

I’m actually knee-deep in this issue for a soon-to-be-released framework-agnostic unit test library I’m working on. As I’m trying to support as many platforms as possible, using custom events really wasn’t a option. Turns out some platforms also don’t have setTimeout (Opera mini, for example), so I ended up using recursion and a try..catch..finally block:

Hi Dean, nice to see you blogging again (did anybody said it?)
I like the idea but I am not sure about one thing, the onpropertychange in the main DOM element. In FireFox we tested (me and other guys) an incremental and generic performances slowdown using DOMAttrModified event in the main node. I wonder if your choice could affect IE performances somehow, even if the callback is pretty simple.
In few words, do you really need real the main node to fire these events? Why don’t you create just a single one “out of the box” to perform the same operation?
Something like:

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

Good solution. I have used the try/catch approach recently in one of my projects and collect all errors in an Array along with the function name in which they occured. It’s really only a small init file, so it works fine. Will consider your approach on one of the next refactoring sessions.

Comment by: G

Posted: 2009/03/30 10:05 pm

Comment: #37

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

Hi Dean; it’s indeed great to see you blogging again; and a very interesting solution you came up with.

Just wondering if you had any chance to do some profiling on your code though, since (at least for IE) it seems like it touches the DOM. If it does, it might be significantly slower than a pure-JS solution…

For the record, YUI keeps a “lastError” property in YAHOO.util.CustomEvent with the latest silenced error, so you can still get to it, albeit I do agree it would be best to get “JavaScript to break where the actual error is”.

And Tobie’s solution does seem very interesting… have to go and try it (no pun intended).

Please note folks, that the code I posted was an example. If you want to implement such a system then you will have to test and benchmark it yourself. For the record, in base2 I create a <meta> element to trap these changes as described in my comment above.

Better (but slower) custom events…
Hurray, Dean Edwards (of IE7 fame) is blogging again. And he has an interesting take on Javascript custom events…His solution is indeed very good for the fact that it doesn’t resort to any pushing/shoving of exceptions to be able to use those except…

Yes, it’s slower. But how many listeners are you dispatching to? Ten or twenty at most, but more likely one, perhaps two. Sometimes people measure the wrong things. It’s what happens inside an event listener that’s important. The dispatch time is practically irrelevant.

Though, even if that “DOMContentLoaded” events got more cross-browser-compatible and precise. I still like that idea:

<script>main()</script></body>

And main() does all the rest. Well I know, this doesn’t work for all that little snipplets doing some weird mostly helpfulstuff, I want to throw that option in. Shame on me, if sb posted that one, I didnt read all the coments… *g*

@Dean, I don’t really have a practical example to hand… just think complex financial application with lots of streaming prices (via Comet) triggering events to multiple components, at the rate of ~10/second.

The sort of thing that your grandma said should never be done with HTML and Javascript…

Dean’s approach works well, but I afraid of performance when such dispatching is used on mousemove event for instance. Try/Finally works much better, except error stack is removed since finally has to be called. This makes debugging a bit harder.

I am new to this callback implementation of javascript. After reading your blog, I got it for firefox. But for IE 7, onpropertchange event never get fire, so I am not able to run your code in IE 7.

Comment by: @newLearner

Posted: 2009/09/02 8:16 am

Comment: #60

[…] you don’t want that to block execution of the other callbacks or of the rest of your method. Dean Edwards wrote about this last year, though his post specifically deals with using the DOM to dispatch events since it […]

Another drawback of using setTimeout() is that it affords the browser to do something in between the callback executions. For example, if you’re implementing a graphically intense application, you may not want to incur redraws and reflows between each callback running.

I released a project that uses this technique to solve problems with async data sources and race conditions. Since IE still doesn’t support custom events, my library doesn’t support it, but you can find the other test casts and which data sources are supported on my project page:
http://code.google.com/p/proto-q/

Great work, Dean. The perfect way to maintain DOM semantics when implementing an observer.
If performance is a concern then you can register each callback as a listener directly. This jsperf tests show it is significantly faster than dispatching a new event for each one. Obviously try/catch is going to be faster than either of these…

First off thanks for this write up, this really helped me a lot. I’m having some trouble with this in MSIE browsers. Instead of changing the property of ‘document.documentElement’, I’m changing the property of an ‘object’ element and I can’t seem to get the listener to fire when the property is changed.

I noticed that the one is of type ‘DispHTMLHtmlElement’ and the other is ‘DispHTMLObjectElement’. Is there any difference in the two?