Skip to content
Advertisement

Why jQuery event loop is interrupted on exception [closed]

After a weird behaviour of our application (using strophe XMPP and jQuery), we have discovered that the jQuery event loop is synchronous and does not catch exception.

It means that if the first event handler raises an exception, the second one is never called.

$(document).ready(function() {
    $(document).bind('foo', onFoo);
    $(document).bind('bar', onBar); 

    $(document).trigger('foo');
    $(document).trigger('bar');
});

function onFoo(e) { 
    console.log('listener onFoo');
    throw 'fail onFoo';
}

function onBar(e) {
    console.log('listener onBar'); // not called
}

We expected to see two outputs, but the second one : “listener onBar” was never displayed.

See JQuery code, in “trigger” function, there is no try/catch pattern during the loop of the handlers.

while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
    event.type = i > 1 ?
        bubbleType :
        special.bindType || type;

    // jQuery handler
    handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
    if ( handle ) {
        handle.apply( cur, data );
    }
... (line 4998 in JQuery 1.10.2)

We have been surprised by this implementation.

In pure JavaScript, all the handlers are called even if one of them crashed: http://jsfiddle.net/bamthomas/kgS7A/2/.

Does someone know why jQuery team does not allow the execution of the next handler even if the previous one crashed ? Why exceptions are not caught?

Why didn’t they use JavaScript event handler?

Advertisement

Answer

This happens because of this loop taken from the .dispatch source:

while ((handleObj = matched.handlers[j++]) && !event.isImmediatePropagationStopped()) {

    // Triggered event must either 1) have no namespace, or
    // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
    if (!event.namespace_re || event.namespace_re.test(handleObj.namespace)) {

        event.handleObj = handleObj;
        event.data = handleObj.data;

        ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler)
        .apply(matched.elem, args);

        if (ret !== undefined) {
            if ((event.result = ret) === false) {
                event.preventDefault();
                event.stopPropagation();
            }
        }
}

As you can see, there is no try/catch around .apply.

That’s the way it has been for years and years and years.

Even if they wanted, changing it now will break too much existing code. Remember, lots of things in jQuery that seem arbitrary now were born in another time.

You can of course ‘fix’ this in your own code (wrapping it in a try/catch with error message), but you’ll surprise pretty much everyone else.

Advertisement