31

I have an event handler, but I want to make sure that it will wait until all the other events in the event queue have executed.

For example, I have some tabs on a page. I have some general code for handling tabs that sets a tab to "Active Tab" when a user clicks on it.

$('a.tab').click(function() {
  $(this).parent('div').find('a.tab').removeClass('activeTab');
  $(this).addClass('activeTab');
});

For a specific set of tabs, I might have a handler:

// handler-1
$('div#myTabs a.tab').click(function() {
  do_something();
});

function do_something() {
  var type = $('div#myTabs a.activeTab').data('type');
  do_something_else(type);
}

do_something() might get called in response to other actions besides clicking on a tab, but regardless of how it gets invoked, it wants to know what the active tab is before it can continue.

My problem is that if it is getting invoked by the code under '// handler-1', the class 'activeTab' may not have been reset yet, so the value it retrieves for 'type' will still be from whatever tag was selected prior to clicking on this one.

I solved it like this:

// handler-1
$('div#myTabs a.tab').click(function() {
  set_timeout( function() {
    do_something();
  }, 100);
});

This looks like too much of a kludge to satisfy me. How do I know that the timer event handler will get called only after all the other handlers have had their turn? Sure I could change "100" to "1000" or even "10000" but I would also be trading performance for more certainty (and there is still no certainty since even 10 seconds may not be enough on a browser instance on a machine that is crippled enough).

I have seen several possible solutions:

but neither of these are general enough to satisfy me. I'm not trying to get a handler to run first. Rather, I want something like this:

function do_something() {
  $.when_all_other_events_have_been_handled(function() {
    do_something();
  });
});

Does anything like this exist?

I looked at $.fn.when(arg).done(), but when what? "arg" in this case would have to be a deferred object that executes whatever is already in the run queue. A Catch-22!

Sproutcore has something like "engine.run()" (don't remember the exact name) that will return only when all other events in the "engine" have been handled, but I can't find anything like that in jQuery. (http://api.jquery.com/category/events/). If anyone knows of something like that, please let me know!

Thanks! :)

Community
  • 1
  • 1
Lawrence I. Siden
  • 9,191
  • 10
  • 43
  • 56

2 Answers2

33

Try this:

function postpone( fun )
{
  window.setTimeout(fun,0);
}

so if you will call postpone(do_something); that do_something will be executed after all UI events that are in the event queue for the moment of postpone() call.

c-smile
  • 26,734
  • 7
  • 59
  • 86
31

I may have found the answer here: http://ejohn.org/blog/how-javascript-timers-work/

According to J. Resig, if I do

setTimeout(function() {
  do_something();
}, 0);

the anonymous function will get enqued immediately but will not get called until everything else on the event queue has already been handled. That is, as long as whatever browser its running really runs everything on a first-in-first-out basis as I would expect it to. If for any reason the Javascript engine takes it upon itself to re-order events in the queue (I don't know why it should, but you never know since AFAIK I've never seen any such guarantee in the standards, but then I need to look again) then all bets are off!

Lawrence I. Siden
  • 9,191
  • 10
  • 43
  • 56
  • Not seeing this behavior with Chrome at the moment. I have 2 separate functions called on the "click" event of a checkbox. On the one I want last I wrapped it in the setTimeout but if I set it to 0.. it seems to be a race condition. I set it to 500 and it seems to work, but I fear it is only by luck. – Dss Sep 17 '13 at 17:26