13

I am working on client side scripts and need to do heavy computations like pushing huge number of objects in an array, it causes JavaScript to stop response and browser hangs giving an alert:

Browser Warning

Is there any best practices or design patterns for handling these computation, I search and find many different ways to handle these situation but solutions are difficult to implement so I need best practices and easy to understand?

(I am writing code just for example But I need a general solution that is cross-browser i.e, multi-threading etc)

Example Code (series contains thousands of objects):

for (var series = 0; series < chartObj.masterChart.series.length; series++) {
    var detailData = [];
    jQuery.each(chartObj.masterChart.series[series].data, function (i, point) {
        if (point.x >= chartObj.RangeSelectedMinValue && point.x <= chartObj.RangeSelectedMaxValue) {
            detailData.push({
                x: point.x,
                y: point.y
            });
        }
    });
    chartObj.detailChart.series[series].setData(detailData);
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Zaheer Ahmed
  • 28,160
  • 11
  • 74
  • 110
  • 1
    In my opinion Javascript shouldn't do any heavy computation. Heavy means huge number of data or realy difficult algorithms which take a lot of time. – Tomasz Dzięcielewski Dec 19 '12 at 07:44
  • Show us some code or tell logic to comment – closure Dec 19 '12 at 07:46
  • That's quite an open topic. Posting some code to make it specific would be a better step. – coderLMN Dec 19 '12 at 07:46
  • Many things are difficult to implement. The solution is not to find easier things but to try harder. – JJJ Dec 19 '12 at 07:46
  • So, what are you doing to cause that? We can't help you optimize your code without a clue of what you're doing. – Cerbrus Dec 19 '12 at 07:52
  • I agree, javascript should not be use for these computation, but If someone have to use this then what is best to implement? I can write code but want open solution that can be implemented anywhere. – Zaheer Ahmed Dec 19 '12 at 07:54
  • `setTimeout` with proper continuations every now and again? – John Dvorak Dec 19 '12 at 07:55
  • 2
    @ZaheerAhmed: We can say things like "Avoid **Massive** Loops, Avoid **Huge** arrays / objects, [don't use heavy operations in each iteration of a loop, if it can be done outside of it](http://stackoverflow.com/questions/13917139/fastest-way-to-iterate-pixels-in-a-canvas-and-copy-some-of-them-in-another-one), but all of that is **way** to generic for the StackOverflow Q&A format. – Cerbrus Dec 19 '12 at 07:57
  • I'd say web workers were created for this purpose. Only works in *real browsers*™ though. http://dev.w3.org/html5/workers/ https://developer.mozilla.org/en-US/docs/DOM/Worker https://developer.mozilla.org/en-US/docs/DOM/Using_web_workers – Tetaxa Dec 19 '12 at 08:04
  • Not to degrade your answer or anything, but support for workers [isn't universal](https://developer.mozilla.org/en-US/docs/DOM/Using_web_workers#Browser_Compatibility), yet. Depending on the browser used, this can be a **very** (if not "the") good option, though. – Cerbrus Dec 19 '12 at 08:16
  • Hence the "real browsers" comment. – Tetaxa Dec 19 '12 at 08:20
  • Whoops, missed that, my bad -.- – Cerbrus Dec 19 '12 at 08:22
  • +1 Nice!! but need to support IE7 and IE8 too :( – Zaheer Ahmed Dec 19 '12 at 10:19

3 Answers3

5

Okay, looking at your code, there's a few things you can optimize:

var s = chartObj.masterChart.series, // #1
    sLength = s.length,              // #2
    chartMin = chartObj.RangeSelectedMinValue,
    chartMax = chartObj.RangeSelectedMaxValue;
for (var series = 0; series < sLength; series++) {
    var detailData = [],
        data = s[series].data,       // #3
        length = data.length;        // #2
    for(var i = 0; i < length; i++){ // #4
        var point = data[i];
        if (point.x >= chartMin && point.x <= chartMax) {
            detailData.push({
                x: point.x,
                y: point.y
            });
        }

    }
    chartObj.detailChart.series[series].setData(detailData);
}
  1. You're getting the same "deeper" object inside chartObj multiple times --> Assign it to a temporary variable;
  2. Don't calculate the length for every iteration of the loop. Same principle as #1
  3. Assign s[series].data to a temp var. This provides a direct pointer to the data instead of having to access s[series].data each iteration of the loop. Same principle as #1
  4. jQuery is slow. For a simple loop, use JavaScript instead, especially if you're looping through a massive object.

I'm not saying this edit will work miracles, but it should reduce the load a bit.

Cerbrus
  • 70,800
  • 18
  • 132
  • 147
  • Thank you nice optimization, implemented!!! :) but still facing problems that is why I ask a general solution? -- Nice +1 – Zaheer Ahmed Dec 19 '12 at 08:23
  • I think those 4 points are pretty much as "general" as I can make it. Although, I found 1 more edit I could make. (`chartMin /chartMax`) – Cerbrus Dec 19 '12 at 08:26
5

You should use WebWorkers

They are really supported and they are real threads in javascript as they spawn real OS threads!

Example

main.js

var heavy_process = new Worker('heavy_process.js');

heavy_process.addEventListener('message', function(e) {
  // Log the workers message.
  console.log(e.data);
}, false);

heavy_process.postMessage();

heavy_process.js:

for (var series = 0; series < chartObj.masterChart.series.length; series++) {

  var detailData = [];
  jQuery.each(chartObj.masterChart.series[series].data, function (i, point) {
      if (point.x >= chartObj.RangeSelectedMinValue && point.x <= chartObj.RangeSelectedMaxValue) {
        detailData.push({
            x: point.x,
            y: point.y
        });
      }
  });
  chartObj.detailChart.series[series].setData(detailData);
  // you should use self.postMessage here !!!
}
johnnydev
  • 164
  • 1
  • 4
2

You could split it in to different "threads" by using timeouts. Like so:

var counter;

function longRun(start) {
    counter = 0;

    for (var i = start; i < 3000; i++) {

        counter++;
        console.log(i);
        if (counter > 99) {
            setTimeout(function() {
                longRun(i+1)
            }, 0);
                console.log('counter at: ' + i + ' start fake "thread"');
            return;
        }
    }
    alert('Done!');
}
longRun(0);​

jsFiddle example

I guess it would prevent the warning, but I don't know how sane it really is.

Karl Johan
  • 4,012
  • 1
  • 25
  • 36
  • 5
    I am fully aware that there are no threads per se in JavaScript, hence the quotation marks. But in this specific case the method of splitting the code into chunks makes the program handle 100 iterations at a time works. I think I made it clear with the quotation marks and the log statement in the code that I am not talking about real threads. It was a word I choose to compare it to another know pattern. I am Sorry if I was confusing anyone – Karl Johan Jun 08 '15 at 19:51
  • Not only does JavaScript not have threads, it has no concept of parallel execution whatsoever. setTimeout or no, it's impossible to have two functions running at the same time - they will always be run in serial. So this solution won't really do anything but slow down your code. – thedayturns Jan 16 '17 at 05:02
  • 2
    At no point in my answer did I say that my solution would execute functions in parallel.The point is to exit the currently running function and start again with a new start value. The main point here was to get rid of the browser warning, which my example did. – Karl Johan Jan 16 '17 at 06:56