41

This seems silly, but I can't find how to do an asynchronous function call with jQuery that doesn't involve some server-side request. I have a slow function that iterates through a lot of DOM elements, and I want the browser to not freeze up while this function is running. I want to display a little indicator before the slow function is called, then when the slow function returns, I want to hide the indicator. I have the following:

$('form#filter', parentNode).submit(function() {
  var form = $(this);
  indicator.show();
  var textField = $('input#query', form);
  var query = jQuery.trim(textField.val());
  var re = new RegExp(query, "i");
  slowFunctionCall(); // want this to happen asynchronously; all client-side
  indicator.hide();
  return false;
});

Currently I submit the form and the indicator isn't displayed, the browser freezes, and then slowFunctionCall is finished.

Edit: I used Vivin's answer, specifically the Sitepoint link to get the following solution:

var indicator = $('#tagFilter_loading', parentNode);
indicator.hide();
var spans = $('div#filterResults span', parentNode);
var textField = $('input#query', parentNode);
var timer = undefined, processor = undefined;
var i=0, limit=spans.length, busy=false;
var filterTags = function() {
  i = 0;
  if (processor) {
    clearInterval(processor);
  }
  indicator.show();
  processor = setInterval(function() {
    if (!busy) {
      busy = true;
      var query = jQuery.trim(textField.val()).toLowerCase();
      var span = $(spans[i]);
      if ('' == query) {
        span.show();
      } else {
        var tagName = span.attr('rel').toLowerCase();
        if (tagName.indexOf(query) == -1) {
          span.hide();
        }
      }
      if (++i >= limit) {
        clearInterval(processor);
        indicator.hide();
      }
      busy = false;
    }
  }, 1);
};
textField.keyup(function() {
  if (timer) {
    clearTimeout(timer);
  }
  /* Only start filtering after the user has finished typing */
  timer = setTimeout(filterTags, 2000);
});
textField.blur(filterTags);

This shows and hides the indicator and also doesn't freeze the browser. You get to watch the DOM elements being hidden as it works, which is what I was going for.

Community
  • 1
  • 1
Sarah Vessels
  • 30,930
  • 33
  • 155
  • 222
  • I think all JS runs in a single thread. So it isn't possible to run asynchronously functions. But I might be wrong about this :) – PeeHaa Jul 26 '11 at 20:40
  • 1
    Depending on how you're accessing the DOM elements that are slowing down your function you might be able to speed up this part. Are you accessing the elements with JQuery using classes or attribute filters (slow)? Does the function run more than once and is there any way you can cache the elements / IDs that you're interacting with? Could you do the same DOM updates by altering a CSS class that all affected elements use? – tomfumb Jul 26 '11 at 20:56

3 Answers3

30

Javascript runs in a single thread and therefore if you have a slow function it will block everything else.

UPDATE

That will do some of what you want, but keep in mind that they are not widely supported supported in IE (I think they will be in IE10).

Some resources on Web Workers:

Here are a few resources on accomplishing multi-threading without web workers. It's important to note that this isn't "true" multi-threading:

Community
  • 1
  • 1
Vivin Paliath
  • 94,126
  • 40
  • 223
  • 295
  • Well, no wonder I can't find some tutorial about this... Thanks. – Sarah Vessels Jul 26 '11 at 20:41
  • You sir, would have been correct in the past, now some browsers support web workers. – nwellcome Jul 26 '11 at 20:42
  • 1
    @nwellcome I've mentioned it as well, but it's not a viable option due to the fact that it is not widely supported. I hope that eventually they will be widely supported, because that would be tremendously useful. – Vivin Paliath Jul 26 '11 at 20:49
  • True, though you could detect support for workers and make a better experience for those who will use modern browsers: http://diveintohtml5.org/detect.html#workers – nwellcome Jul 26 '11 at 20:57
  • @nwellcome I should clarify my earlier statement. It's only IE that doesn't support it. – Vivin Paliath Jul 26 '11 at 20:59
  • @Vinin evidently IE support is planned for IE10... I don't know how yet but somehow the implementation will be broken. http://blogs.msdn.com/b/ie/archive/2011/07/01/web-workers-in-ie10-background-javascript-makes-web-apps-faster.aspx – nwellcome Jul 26 '11 at 21:02
  • 1
    @nwellcome, yes I saw that it's planned for IE10. But as you said, knowing Microsoft it'll probably be "different" in small, yet significant ways! – Vivin Paliath Jul 26 '11 at 21:02
6

I was going to suggest looking at a timeout but alas. This post by John Resig (of jQuery) explains a bit about how JavaScript handles its single thread. http://ejohn.org/blog/how-javascript-timers-work/

This article also explains: "One thing to remember when executing functions asynchronously in JavaScript, is all other JavaScript execution in the page halts until a function call is completed. This is how all the current browsers execute JavaScript, and can cause real performance issues if you are trying to call too many things asynchronously at the same time. A long running function will actually "lock up" the browser for the user. The same is true for synchronous function calls too."

All that said, it's possible you could simulate an asynchronous function call yourself by breaking whatever loop you are doing into a smaller chunk and using setTimeout().

For instance this function:

// Sync
(function() {
  for(var x = 0; x < 100000; ++x) {console.log(x)}
})()

// ASync
var x = 0;
setTimeout(function() {
   console.log(x++);
   if(x < 100000) setTimeout(arguments.callee, 1);
} ,1)
slifty
  • 13,062
  • 13
  • 71
  • 109
  • If you don't want to use arguments.callee (it is forbidden in strict mode since ES5), you could give the callback function a name and use its name in next calls. For example: var x = 0; setTimeout(function rec() { console.log(x++); if(x < 100000) setTimeout(rec, 1); } ,1) – danpop Aug 03 '17 at 11:47
6

You might want web workers!

Edit: I'm surprised how many people jumped to "its not possible" so I will elaborate. Web Workers are part of the HTML 5 spec. They allow you to spawn threads to run scripts in the background without blocking the UI. They must be external js files, are invoked by

var worker = new Worker('my_task.js');

and are communicated to via events.

nwellcome
  • 2,279
  • 15
  • 23
  • Might be something. That page says "As usual, background threads—including workers—cannot manipulate the DOM", though. – Sarah Vessels Jul 26 '11 at 20:44
  • Gooood point, workers cannot access `window`, `document` or `parent` so no DOM manipulation. – nwellcome Jul 26 '11 at 20:53
  • @nwellcome, it's not that they are *not* possible :) I'm pretty excited about the possibilities with Web Workers. The problem is that they are not widely supported (yet). – Vivin Paliath Jul 26 '11 at 20:55
  • 2
    @Sarah: yes workers don't access the DOM because the DOM is not multi-thread safe. So the worker should run hardcore calculations and let the caller manipulate the DOM. – Ruan Mendes Jul 26 '11 at 21:37