Browser hangs for after computing intense JS function in a loop - javascript

My problem requires the below function to run in a loop on an average of 20 iterations
var test = function() {
// some logic which take about 0.5 sec to execute
// and deal with DOM manipulation
};
so if I iterate over it 20 items it will take an average of 10 sec and in the meantime browser hangs (not able to scroll down).
So I tried to change it to something like this
var test = function() {
setTimeout(function() {
// some logic which take about 0.5 sec to execute
// 0.5 sec as it deals with DOM manipulation
}, 0);
};
so that browser gets time to execute after each time Out but still the browser in hanging.
Why it is so? This is what I have always read in theory but not working practically.

This is how JavaScript works, whenever you block the main thread, your site will become unresponsive to events.
You should really try to make parsing your templates more efficient. 10s is an eternity when it comes to UX.
If that is not an option, you could look into moving some of the logic into a different thread and execute it asynchronously via web workers and only do the actual DOM manipulation in your main thread.

Related

True thread blocking in javascript

Background:
The following code demonstrates that the alert function blocks the operation of setTimeout:
// clock time
function now(){ return (new Date()).getTime() }
var start = now(),
elapsed_before_interruption
// This will interrupt the function below
setTimeout(function(){
elapsed_before_interruption = now()-start
alert('Paused')
start = now()
}, 2000)
setTimeout(function(){
var elapsed_since_interruption = now() - start
var elapsed = elapsed_since_interruption + elapsed_before_interruption
// drop < 1/100s from display
var t = Math.round(100 * elapsed / 1000)/ 100
// Always finishes ~4 seconds after (i.e has been interrupted)
alert( 'Elapsed time: ' + t 's' )
}, 4000)
fiddle
This makes for an excellent pause mechanism when I am choreographing various function calls based upon predetermined intervals.
My question:
Can this be done without calling alert?
EDIT
Whilst similar to the following question: What is the JavaScript version of sleep()? the responses to that question predominantly make the assumption that the OP is dealing with a scheduling issue and suggest code restructuring. I would like to keep this question up as I am specifically not asking for advice on working with promises etc.
By means of explanation, I am writing single web pages (only my code) that are used to conduct timed response latency trials. There are many moving parts and I can implement pause by simply writing
function pause(){ alert('paused') }
I however have to introduce the ugly browser dialog. Anyway to avoid this?
The very short answer: Don't do it this way. If you need to control the order of async processes, there are a variety of better ways.
For debugging, use the Chrome dev tools to set a breakpoint. Much easier.
If you want to wait for something specific, approaches like Promises, callbacks, and libraries like async or queue are relatively easy to use and more flexible.
If you want the syntactical simplicity of waiting in the middle of a function, try precompiling your code with babel and using the async/await syntax, which behaves in a similar way to what you have here (though you need to wait for something specific).
Note that none of these are "true thread blocking". There's only one thread in JS (unless you're using Workers or similar); you can block it with something like while(true) {}, but it locks everything in your browser window, including user interactions. You don't actually want that.

Understanding async using setTimeout

I have a UI where I need animations to run smoothly. Every so often, I need to do a semi-large data calculation that makes the animation skip until it is this calculation is completed.
I am trying to get around this by making the data calculation async with setTimeout. Something like setTimeout(calcData(), 0);
The whole code is something like this (simplified):
while (animating) {
performAnimation();
if (needCalc) {
setTimeout(calcData(), 0);
}
}
But I still get a skip in the animation. It runs smoothly when I do not need to do any data calculations. How can I do this effectively? Thanks!
You are seeing the skip because only one javascript thread is run at once. When something is done asynchronously the javascript engine puts it a queue to be ran later, then finds something else to execute. When something in the queue needs to be done the engine will pull it out and execute it, blocking all other operations until it completes.The engine then pulls something else out of its queue to execute.
So if you want to allow your render to run smoothly you must break up your calculation into multiple async calls, allowing the engine to schedule the render operation in between calculations. This is easy to accomplish if you are just iterating over a array, so you can do something like:
var now=Date.now;
if(window.performance&&performance.now){//use performace.now if we can
now=performance.now;
}
function calculate(){
var batchSize=10;//If you have a exceptionally long operation you may want to make this lower.
var i=0;
var next=function(){
var start=now();
while(now()-start<14){//14ms / frame
var end=Math.min(i+batchSize,data.length);
for(;i<end;i++){//do batches to reduce time overhead
do_calc(data[i]);
}
}
if(i<data.length) setTimeout(next,1)//defer to next tick
};
next();
}
calculate();
function render(){
do_render_stuff();
if(animating) {
requestAnimationFrame(render);//use requestAnimationFrame rather then setTimeout for rendering
}
}
render();
Better yet, if you can, you should use WebWorkers which work in a different thread, completely separate from the main js engine. However you are stuck with this if you need to do something you cant do in a WebWorker, such as manipulating the DOM tree.
Firstly, let's talk what's going on in your code:
while (animating) {
performAnimation();
if (needCalc) {
// it should be setTimeout(calcData, 0);
setTimeout(calcData(), 0);
}
}
In line setTimeout(calcData(), 0); really you don't defer calling of calcData function, you call it, because you use () operator after function name.
Secondly, lets think, what's going on when you really make defer calling for calcData in the code above: commonly JavaScript is running in one thread, so, if you have code like this:
setTimeout(doSomething, 0);
while (true) {};
doSomething will never be called, because interpreter of javascript executes while loop forever and it hasn't "free time" to execute other things (even UI) . setTimeout - just say to schedule execution of doSomething when interpreter will be free and it's time to execute this function.
So, when browser executes javascript function, all other stuff become freezing.
Solution:
If you have big data that you need to process, maybe it would be better to make calculations on backend and after send results to frontend.
Usually when you need to make some calculation and render results it's better to use requestAnimationFrame than while loop. Browser will execute function passed in requestAnimationFrame as soon as possible, but also you give browser a time to handle other events. You can see smooth redrawing using requestAnimationFrame for game (step-by-step tutorial here).
If you really want to process huge amount of data at frontend part and you want to make ui works smooth, you can try to use WebWorkers. WebWorkers look like threads in JavaScript, you need to communicate between main UI "thread" and WebWorker by passing messages from one to another and back and calculations on WebWorker don't affect UI thread.
Mostly, your problem boils down to your incorrect usage of setTimeout()
setTimeout(calcData(), 0);
The first argument to setTimeout is a REFERENCE to the function that you wish to call. In your code, you are not referencing the calcData function, you are invoking it because you've included () after the function name.
Second, the fact that you've put 0 for the delay does not mean you will have a 0 second delay before the function runs. JavaScript runs in a single threaded context. The setTimeout function is placed in a queue and executed when the JavaScript engine is available, but no sooner than a minimum of 10ms or the amount you specify (whichever is less).
Realistically, your line should be:
setTimeout(calcData(),10);

create jquery infinite animation not using recursion?

How to use jquery to create infinite animation,
BUT NOT using recursion way?
The recursion solution I found: jQuery .animate() callback infinite loop
The problem of using recursion is:
while animating, the browser tab of currrent page will take MORE and MORE memory.
You have actually made a mistake in assuming that the code is "recursive". It is not recursive. Your comment that "the browser tab of current page will take MORE and MORE memory." is not because of any recursion in that code. If you have a memory leak per second (as per your comments), then the problem lays elsewhere.
The instances of the code actually run sequentially and not via nested stack calls (i.e. linear and not recursive).
Using the most basic example you linked to:
function start() {
$('#element').animate({}, 5000, 'linear', start);
}
start();
here is what actually happens:
A function called start is defined and then called once
Inside start, an animation operation is started, then start immediately exits!
The animate just adds information to a single animation queue for the element, then returns.
The animation queue is processed step by step by a separate process (single timer etc).
When the queued entry has met its expected end state, it calls the callback - The global start function stored in the queued entry.
That simple adds another entry on the animation queue and exits.
Basically the code looks recursive, but as the operations are async, via a queue, it is not recursive. I don't know if there is an official name for these, so I just call them chained callbacks.
UPDATED WITH TEST RESULTS
Conclusion... All methods burn up the same amount of memory and it eventually gets released, doesn't keep building forever so not really an issue.
Example 1
This is the code the OP originally had a problem with memory usage increasing in Chrome.
(function($){
$(function(){ //document.ready
function start() {
$('#animate').animate({'margin-left':'150px'}, 1000, function () {
$(this).animate({'margin-left':'50px'}, 1000, 'linear', start);
});
}
start();
});
})(jQuery);
Example 2
Current solution including a callback as requested by Bergi to avoid potential "drifting" in setinterval.
(function($){
$(function(){ //document.ready
});
(function customSwipe(element) {
element
.animate({"margin-left" : "150px"}, 1000)
.animate({"margin-left" : "50px"}, 1000, function(){
setTimeout(function(){
customSwipe(element);
}, 2000);
});
})($('#animate'));
})(jQuery);
Example 3
Original Answer I gave, using setInterval()
(function($){
$(function(){ //document.ready
setInterval(function(){
$("#animate").animate({'margin-left':'150px'},1000);
$("#animate").animate({'margin-left':'50px'},1000);
},2000);
});
})(jQuery);
Skeleton w/ Jquery
Empty page w/ only the #animate element
(function($){
$(function(){ //document.ready
});
})(jQuery);
DATA AFTER TAB OPEN FOR 10 MINUTES
CODE STARTING ENDED
Example 1 14.300k 19.996k
Example 2 14.300k 20.020k
Example 3 14.300k 20.344k
Skeleton w/ jQuery 14.300k 15.868k
Interesting that the code that did nothing still increased usage slightly. These values go up and down as memory is used and released. Another thing would be to use the "purge memory" button in task manager to see how much of that used memory is garbage waiting to be collected.
DATA AFTER 25 MINUTES
Example 1 14.300k 18.640k
Example 2 14.300k 18.724k
Example 3 14.300k 18.876k
Skeleton w/ jQuery 14.300k 15.868k
Conclusion... All methods burn up the same amount of memory and it eventually gets released, doesn't keep building forever so not really an issue.
So just using the most solid, sound code would be the best option, Example 2 would be my choice.
UPDATED USING CALLBACK
Have you tried using setTimeout with $.animate()?
(function($){
(function customSwipe(element) {
element
.animate({'margin-left':'150px'}, 1000)
.animate({'margin-left':'50px'}, 1000, function(){
setTimeout(function(){
customSwipe(element);
}, 2000);
});
})($('#animate'));
$(function(){ //document.ready
});
})(jQuery);
Remove the setTimeout() if you don't need it to delay between animations.
JSFIDDLE of the above code working...
You might also want to look into this for more intricate animations.
http://api.jquery.com/jquery.fx.interval/
Conclusion... All methods burn up the same amount of memory and it eventually gets released, doesn't keep building forever so not really an issue.
What you are seeing in the Chrome TM is every time it fires the animation, that much memory is requested and the OS "commits" that memory to chrome for the operation. Once the operation is completed, the memory is still committed. At some point Chromes garbage collector comes along and releases that memory and your usage stats will drop back down. So you will see the memory going up and down if you watch it long enough.
You can put --purge-memory-button at the end of your Chrome command line to have a Purge Memory button available in Chrome TM. This might help you to see how much memory is actually waiting to be released.
Hope this answer helps you and maybe some others.

Timing in javascript sequencer

I have developed a music sequencer in javascript; something like this: http://stepseq.michd.me/
I have implemented loop using setInterval function in following way:
var Sequencer = {
...
// every step sequencer ...
next: function(callback) {
// restart from begin if arrive to last sequecer step
if(Sequencer.current==Sequencer.steps.length)
Sequencer.current = 0;
// play all sounds in array step contains
if(Sequencer.steps[Sequencer.current].length>0) {
var set = Sequencer.steps[Sequencer.current];
for(var i=0;i<set.length;i++){
set[i].play();
}
}
callback(Sequencer.current);
Sequencer.current++;
},
loop: function(callback) {
Sequencer.interval = $interval(function(){
Sequencer.next(callback);
}, Sequencer.time);
}
}
...
Code below works but i think that there is a better way to implement loop; infact sometimes steps goes out of time. Sequencer.time (time passed to setInterval function) is a time in millisecs (this value is the conversion of a bpm value; for example it can be 125),
Someone can suggest me a better solution?
N.B.: this is a web application angularjs based (for this reason in code above a use $interval insteed of setInterval), but i think that this point is insignificant.
Javascript timer intervals are not guaranteed to run at exactly the time you request, due to the single threaded nature of JS. What you get is a callback that is queued up to run after the interval expires, whenever the engine is free to run it.
John resig has covered this off in some depth:
http://ejohn.org/blog/how-javascript-timers-work/
http://ejohn.org/blog/analyzing-timer-performance/
And from his conclusions, which is going to be important for your app:
If a timer is blocked from immediately executing it will be delayed
until the next possible point of execution (which will be longer than
the desired delay).
I don't really have a better solution for your problem, due to these fundamental issues with timers in JS, but this may at least explain what is happening.

Is it possible to 'thread' Javascript and keep access to the UI?

I would like to thread some Javascript code while both the main process and thread are free to update the browser UI.
For example:
function StartStuff() {
StartThreadedCode();
// do more work and update the UI while StartThreadedCode() does its work
}
function StartThreadedCode() {
// do stuff (do work and update the UI)
}
Is it possible?
There are two main ways to achieve "multithreading" in Javascript. The first way is a cross-browser solution, that would also work in older browsers, but is more complicated to implement.
The idea behind it is that you give the UI some time to update every once in awhile. Since there's no synchronous sleep function in Javascript, the only way to achieve this is to use setTimeout (or setInterval with a little bit more complicate logic) to delay the execution of every loop of your complex calculations. This would give the browser some time to update the UI between loops, giving the visual effect of multiple things happening simultaneously. A few ms should be more than enough for the UI to reflect the latest changes.
It has it's drawbacks of course, and can be quite difficult to implement if there are multiple actions the user might want to do while the background calculations are being performed. Also it can drastically slow down the whole background calculation, since it's delayed a few ms now and then. In specific cases, however, it does the trick and performs well.
The second option would be to use web workers, that are basically Javascript scripts running independently in the background, like a thread. It's much easier to implement, you only have to worry about messaging between main code and background workers, so your whole application isn't affected as much. You can read about using them from the link posted by Mic https://developer.mozilla.org/en/Using_web_workers. The greatest drawback of web workers is their support by browsers, which you can see at http://caniuse.com/#search=worker There's no possible workaround for IE <9 or mobile browsers that truly simulate the effect, so there's not much you can do about those browsers, but then again, the benefits of modern browsers might outweigh poor IE support. This, of course, depends on your application.
Edit: Im not sure whether I explained the first concept clearly enough, so I decided to add a small example. The following code is functionally equivalent to:
for (var counter = 0; counter < 10; counter++) {
console.log(counter);
}
But instead of logging 0-9 in quick succession, it delays 1s before executing the next iteration of the loop.
var counter = 0;
// A single iteration of your calculation function
// log the current value of counter as an example
// then wait before doing the next iteration
function printCounter() {
console.log(counter);
counter++;
if (counter < 10)
setTimeout(printCounter, 1000);
}
// Start the loop
printCounter();
As of 2009 (FF 3.5/Gecko 1.9.1) a new Web API was added that is called Web Workers. It works also on Chrome 4+, Opera 10.6+ and IE10+.
The worker is basically a background thread that runs in a separate process.
The communication between the master process (eg. your UI's main thread) and the slave process (the background thread, the worker) is established with the aid of a generic PostMessage/onmessage function where you can exchange whatever data you like between the two parties.
It is worth mentioning that every single worker is assigned to a different core. For instance by creating 4 different workers (that do a long-running computation) on a quad-processor you are going to see all the 4 CPU-cores like 100% while the main-script is still idling and thus responding to your UI events (look at this example).
A basic example:
main-script.js
if ("function" !== typeof window.Worker) {
throw "Your browser doesn't support Web Workers";
}
var thread = new Worker("background-thread.js");
thread.onmessage = function(e) {
console.log("[A] : I received a " + e.data + " :-P");
};
thread.onerror = function(e) {
console.log(e);
};
console.log("[A] : I'm sending a PING :-)");
thread.postMessage("PING");
background-thread.js
onmessage = function(e) {
console.log("[B] : I receveid a " + e.data + " :-)");
console.log("[B] : I will respond with a PONG ;-)");
postMessage("PONG");
};
The above example should produce the following output at your browser's console:
[A] : I'm sending a PING :-)
[B] : I receveid a PING :-)
[B] : I will respond with a PONG ;-)
[A] : I received a PONG :-P
So happy PING-ing to your background script!
Javascript is single threaded language, but you can do some tricks to imitate multithreading:
http://www.nczonline.net/blog/2009/08/11/timed-array-processing-in-javascript/
http://www.nczonline.net/blog/2011/09/19/script-yielding-with-setimmediate/
http://www.nczonline.net/blog/2011/05/03/better-javascript-animations-with-requestanimationframe/

Categories