How to Avoid Freezing the Browser When Doing Long-Running Computations in JavaScript

How to avoid freezing the browser when doing long-running computations in Javascript

If you only need to do a calculation and don't need to access the DOM during the long running calculation, then you have two options:

  1. You can break the calculation up into pieces and do a piece at a time on a setTimeout(). On each setTimeout() call, the browser will be free to serve other events and will keep the page alive and responive. When you finish the last piece of the calculation, you can then carry out the result.
  2. You can run the calculation in the background using a webworker in modern browsers. When the calcuation is done in the webworker, it sends a message back to the main thread and you can then update the DOM with the result.

Here's a related answer that also shows an example: Best way to iterate over an array without blocking the UI

Prevent long running javascript from locking up browser

if you can turn your calculation algorithm into something which can be called iteratively, you could release control back the browser at frequent intervals by using setTimeout with a short timeout value.

For example, something like this...

function doCalculation()
{
//do your thing for a short time

//figure out how complete you are
var percent_complete=....

return percent_complete;
}

function pump()
{
var percent_complete=doCalculation();

//maybe update a progress meter here!

//carry on pumping?
if (percent_complete<100)
{
setTimeout(pump, 50);
}
}

//start the calculation
pump();

How to stop intense Javascript loop from freezing the browser

I would ditch the "each" function in favour of a for loop since it is faster. I would also add some waits using the "setTimeout" but only every so often and only if needed. You don't want to wait for 5ms each time because then processing 3500 records would take approx 17.5 seconds.

Below is an example using a for loop that processes 100 records (you can tweak that) at 5 ms intervals which gives a 175 ms overhead.

var xmlElements = $(xmlDoc).find('Object');
var length = xmlElements.length;
var index = 0;
var process = function() {
for (; index < length; index++) {
var toProcess = xmlElements[index];
// Perform xml processing
if (index + 1 < length && index % 100 == 0) {
setTimeout(process, 5);
}
}
};
process();

I would also benchmark the different parts of the xml processing to see if there is a bottleneck somewhere that may be fixed. You can benchmark in firefox using firebug's profiler and by writing out to the console like this:

// start benchmark
var t = new Date();
// some xml processing
console.log("Time to process: " + new Date() - t + "ms");

Hope this helps.

How to process the big loop without freezing the browser using setTimeOut function in javascript?

You can use a loop variable, like the following code. In this example, the function increases every element by 1. The timeout period is 1 millisecond.

    var currentIndex;
function processNthElement(array) {
if (currentIndex >= array.length)
{
//the whole array has been processed, do what you need to do
//with the results here
return;
}

//do what you want with the array element here
array[currentIndex]++;

currentIndex++;

setTimeout(function () {
processNthElement(array);
}, 1);
}
function processArrayWithSetTimeout(array) {
currentIndex = 0;
processNthElement(array);
}

Then to process a large array, just call processArrayWithSetTimeout(array). However since we are using timeout, you need to process the result at the very last function call(see the comment in the function). If an array has 10000 elements, it will take more than 10000 milliseconds or 10 seconds to process, but the UI won't be freezed.

Note that this still processes the array sequentially but without freezing the UI as it waits for a while after processing 1 element.

javascript loop freezes browser and can't see change before loop

You have to add a small delay after changing the color before running the heavy process:

function runCode() {
$("#sample-span").toggleClass("colorized");
setTimeout(runTask,10)
}

function runTask(){
for (var i = 0; i < 100000; i++) {
console.log(new Date());
}
$("#sample-span").toggleClass("colorized");
}

JSFiddle

DOM not updating during long function

This is how browsers work. (And Electron's display is effectively a browser.) There's a single thread which is responsible for both updating the UI and running the client-side JavaScript code. (In Electron it's the "rendering thread.") So while your JavaScript code is running, the UI won't update.

There are two solutions:

  1. Have another thread do the heavy lifting and post updates periodically to the main thread. In a browser you'd do that with a web worker. Apparently you can have web workers with Electron as well, see this example. Or perhaps you could have the work done by your main process rather than your rendering process.

  2. Break up the logic so that you yield back to the browser periodically so it has a chance to update its display.

#1 is probably your better bet for the kind of number crunching you're doing. Here's an example counting from 1 to 1,000,000,000 with updates every 10,000:

// Get the contents of our worker script as a blobvar workerScript = document.getElementById("worker").textContent;var blob = new Blob(    [workerScript],    {        type: "text/javascript"    });
// Create an object URL for itvar url = (window.webkitURL || window.URL).createObjectURL(blob);
// Start the workervar worker = new Worker(url);worker.addEventListener("message", function(e) { if (e && e.data && e.data.type === "update") { display.textContent = "Value: " + e.data.value; }});
<script id="worker" type="javascript/worker">// The type on this script element means the browser// won't run it directly. It's here so we can turn it// into a blob to run the worker later.for (var n = 1; n <= 1000000000; ++n) {    if (n % 10000 === 0) {      self.postMessage({type: "update", value: n});    }}</script><div id="display">Waiting for worker to start...</div>

Avoid forced delay of setTimeout when breaking up long running JavaScript tasks

There is a straightforward workaround for this issue. Since the minimum delay of setTimeout is measured from when the timer is set, make sure to set timers at least 10–15 ms before each chunk should be processed. When several setTimeout are set, they queue up and the next one is invoked immediately after the previous, without the additional delay. This can be done with only 2 active timers:

function runLongTask() {
var complete = false;
function processChunk() {
if(!complete) {
/* ... process chunk, set complete flag after last chunk ... */
//set new timer
setTimeout(processChunk);
} else {
/* ... code to run on completion ... */
}
}
//set a timer to start processing
setTimeout(processChunk);
//set an extra timer to make sure
//there are always 2 active timers,
//this removes the extra delay provided
//that processing each chunk takes longer
//than the forced delay
setTimeout(processChunk);
}

Below is a working demo comparing the workaround approach to the traditional approach of setting a new setTimeout after each chunk is processed. In the workaround there is always an extra setTimeout set ahead, reducing the processing time with about 4 ms or more for each chunk (about 40 ms or more for 10 chunks, as demonstrated below), provided that each chunk takes at least 4 ms to process. Note that the workaround demonstrates the use of only 2 active timers.

function runForAtLeast15ms() {  var d = (+new Date) + 15;  while(+new Date < d);}
function testTimeout(repetitions, next, workaround) { var startTime = +new Date;
function runner() { if(repetitions > 0) { //process chunk runForAtLeast15ms(); //set new timer setTimeout(runner); } else if(repetitions === 0) { //report result to console console.log((workaround? 'Workaround' : 'Traditional') + ' approach: ' + ((+new Date) - startTime) + ' ms'); //invoke next() function if provided next && next(); } repetitions--; }
setTimeout(runner);
if(workaround){ //make sure that there are always 2 //timers running by setting an extra timer //at start setTimeout(runner); }}
//First: repeat runForAtLeast15ms 10 times//with repeated setTimeouttestTimeout(10, function(){ //Then: repeat runForAtLeast15ms 10 times //using a repeated set of 2 setTimeout testTimeout(10, false, true);});


Related Topics



Leave a reply



Submit