4

I have some javascript on my page that takes a very long time to execute (between 10-30 seconds)

The code basically look like that :

//Iterate over all the elements in the array
for(int i=0; i<array.length; i++){
   //Complex stuff for each element
}

The problem is that while this code is executing, the UI is not responsive.

Is there any way to solve this issue? I know javascript has some sort of asynchrony but I never really used it before...

Also, the order the elements are processed must always be the order that they appear in the array.

EDIT : I tried the solution below with setTimeout() and in the "duplicate", but it still doesn't fix my problem. I guess it's because the size of my array is not very big BUT the calculation for each element is pretty big.

Also the UI I want to be responsive is an animated loading gif. Since the calculation for each item is too big, the animation is sloppy.

Gudradain
  • 4,653
  • 2
  • 31
  • 40
  • you can convert the loop body to a function, call it next(), dupe a backwards copy of array like r=array.slice().reverse(), and in next(), get the current value by calling r.pop(). if no value, return, otherwise do your normal stuff and call setTimeout(next, 10) at the end of the function. – dandavis Jun 12 '14 at 20:44
  • More duplicates: http://stackoverflow.com/search?tab=votes&q=%5bjavascript%5d%20iterate%20array%20async – Felix Kling Jun 12 '14 at 21:06

6 Answers6

5

Create a variable and set it to the starting value for your counter.

Create a function that:

  1. Does whatever the body of the loop does
  2. Increments the counter
  3. Tests to see if the counter is still under the length and, if it is, calls the function again

At this point you will have functionality equivalent to what you have already.

To pause between each call of the function, and allow time for other functions to fire, replace the direct call to the function with a call to setTimeout and use the function as the first argument.

var counter = 0;
function iterator () {
    //Complex stuff for each element
    counter++;
    if (counter < array.length) {
        setTimeout(iterator, 5);
    } 
}
Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
5

Javascript uses something called the "Event Loop". Basically, there is a single thread that executes the code for each event as it occurs.

There is a function called setTimeout that you can use to make your Javascript code run in a future iteration of the loop. One trick that some developers use to keep the UI responsive during a long-running Javascript task is to periodically invoke code to be run with a timeout of zero. This cause the code to execute almost immediately without locking the event loop.

For example, you could write

var i = 0;
setTimeout(myFunction, 0);

function myFunction() {
    // complex stuff for each element.
    i++;
    if (i < array.length)
        setTimeout(myFunction, 0);
}

Be aware that on modern browsers, as long as Javascript code is executing, the UI will be unresponsive. Some browsers will even pop up a message urging the user to kill the script; very bad!

Also, in modern browsers, there is a new feature called "Web Workers". Web Workers allow for Javascript code to be executed in a separate thread. You can create as many threads as you like, so consider using web workers as well if your task is easily parallelizable.

Vivian River
  • 31,198
  • 62
  • 198
  • 313
1

If you need the browser to update the page in the middle of your JS code, you need to yield to the browser, e.g. with a setTimeout, in a continuation passing style:

function doStuff(i) {
    // do some stuff with index i
    if (i < array.length) {
        setTimeout(doStuff.bind(window, i + 1), 0);
    }
}

doStuff(0);
univerio
  • 19,548
  • 3
  • 66
  • 68
  • that doesn't guarantee that the actions happen in-order, as required by the question. – dandavis Jun 12 '14 at 20:45
  • @dandavis If `array` is never mutated, why isn't the iteration order guaranteed? – univerio Jun 12 '14 at 20:46
  • 1
    the iteration order is guaranteed, the deferred execution order is not since you use setTimeout. it will usually work as-coded, but not 100% of the time. – dandavis Jun 12 '14 at 20:49
  • 1
    @dandavis Can you explain your reasoning? `setTimeout` will always schedule the next iteration to execute after the current iteration (and any DOM mutations) completes, and therefore the code will execute as if it were written sequentially, but with pauses in between iterations. Of course, if you have additional AJAX calls in each iteration you'll need to add synchronization but that's not required in the question. – univerio Jun 12 '14 at 20:56
  • looking closer, it's ok. not sure why, but it confused me when i first saw it. it actually implements the same basic steps as my first comment... – dandavis Jun 12 '14 at 21:25
1

I tend to do this kind of thing like so:

function doStuff(arr) {
  if (arr.length === 0) {
    // do whatever you need to do when you're done
    return;
  }

  // Do body of computation here

  // Or use setTimeout if your browser doesn't do setImmediate
  setImmediate(function () { doStuff(arr.slice(1)); });
}

doStuff(actualArrayOfThings);

Basically, break your loop body up, and use recursion to run each part of the loop in order. It's pretty easy to break this out into a helper function, since the loop body itself is what varies. Something like:

function eachAsync(arr, body) {
  if (arr.length === 0) {
    // do whatever you need to do when you're done
    return;
  }

  body(arr[0]);

  setImmediate(function () { eachAsync(arr.slice(1), body); });
}

eachAsync(actualArrayOfThings, doStuff);
Chris Tavares
  • 29,165
  • 4
  • 46
  • 63
0

If your task is to process data without interfering with the user interface you can use WebWorkers https://developer.mozilla.org/en/docs/Web/Guide/Performance/Using_web_workers

Note it's supported by modern browsers.

tabalin
  • 2,303
  • 1
  • 15
  • 27
-2

Use setTimeOut() if the for loop is doing some tidy work. It depends upon the complex stuff executing for each iteration.

Also, consider using JQuery/AJAX for executing it asynchronously.

unicorn2
  • 844
  • 13
  • 30
saysiva
  • 810
  • 1
  • 7
  • 11