How to execute heavy javascript code without the browser freezing? - javascript

I had a page which executes heavy javascript code after loading. To prevent the page from freezing upon loading, I spaced the execution into batches with some "no-execution" time in between (using Timeouts), and things worked well.
Lately, I've had to add additional heavy javascript code which can execute upon client actions, yet these actions can occur even before the original heavy script is done executing. This time, spacing the action won't help, since at the "downtime" of one script the other can run and vice versa, which will cause the browser to freeze.
The problem is actually more complicated as there are multiple such actions, each executing a different heavy script, and each script sort of has a different "priority" as to how fast i'd like it to finish, compared to the other ones.
My question is, what is the common practice in such situations? I tried thinking of a way to solve it, but all I could think of was quite a complex solution which would pretty much be like writing an operating system in javascript - i.e., writing a "manager" code which executes every X time (using an "interrupt"), and chooses which "context to switch to" ( = which job should run right now), etc...
This however sounds pretty complicated to me, and I was hoping there might be other solutions out there. My problem sounds like one which I'd assume many people have stumbled upon before, so even if the only solution is what I suggested, I'd assume someone already wrote it, or there is some library support for this.
Any help would be greatly appreciated. Thank you.
== EDIT ==
by "heavy code", I mean for example the DOM manipulation of a great number of elements.

You will need to think of defining your UI/Problem domain as a set of Asynchronous tasks. Here's some more insight http://alexmaccaw.com/posts/async_ui until I formulate a better answer for you.

If you don't want to block your script you can use web workers. See MDN: Using web workers for a good introduction. Note that web workers are still relative new and not supported by most browser.
However, if you want to support all browser and add some kind of priority for your "heavy scripts", you should define something yourself, e.g:
function WorkerQueue(this_argument){
this.queue = [];
this.this_argument = this_argument;
this.priority = 1;
}
WorkerQueue.prototype.enqueue = function(callback){
this.queue.push(callback);
}
WorkerQueue.prototype.dequeue = function(){
return this.queue.splice(0,1)[0];
}
function WorkerPool(){
this.pool = [];
this.status = "running";
this.timeout = null;
}
WorkerPool.prototype.addWorker = function(this_argument){
this.pool.push(new WorkerQueue(this_argument));
return this.pool[this.pool.length - 1];
}
WorkerPool.prototype.nextTask = function(){
var max_priority = 0;
var max_priority_task = this.pool.length;
for(var i = 0; i < this.pool.length; ++i){
if(this.pool[i].priority > max_priority && this.pool[i].queue.length !== 0){
max_priority = this.pool[i].priority;
max_priority_task = i;
}
}
// pool is empty or all tasks have an invalid priority
if(max_priority_task === this.pool.length)
return;
if(this.pool[max_priority_task].this_argument)
this.pool[max_priority_task].dequeue().apply(this.pool[max_priority_task].this_argument);
else
this.pool[max_priority_task].dequeue().apply();
if(this.status !== "running")
return;
this.timeout = setTimeout(function(t){return function(){t.nextTask();};}(this),1000);
}
var Workers = new WorkerPool();
var worker1 = Workers.addWorker();
worker1.enqueue(function(){
console.log("Hello");
});
worker1.enqueue(function(){
console.log("World");
});
var worker2 = Workers.addWorker();
worker2.priority = 2;
worker2.this_argument = worker2;
worker2.enqueue(function(){
console.log("Worker 2 - changing priority");
this.priority = .2;
});
worker2.enqueue(function(){
console.log("Worker 2 - after change");
});
Workers.nextTask();
Demo
In this case, every "heavy script" is a worker, which is basically a queue of tasks. You create a new worker in the pool by using addWorker and add tasks to the specific workers queue by using worker.enqueue(callback).

Related

How to find which js file creating problem

I have more than 20 js files like jQuery etc in my project, when I scroll it gets hung as it loads very lazily. How to find which file creates the problem?
It may be one of several things and without looking at your code I couldn't possibly say what the cause would actually be. You've asked an extremely subjective comment. There's no silver bullet when it comes to debugging and problem solving.
I've always found the Occams Razor approach the most effective.
"When you remove all other options, whatever remains, however unlikely, must be the problem/solution/answer."
Firstly, before you start removing JS files, have you tried doing a full-search of your project for .scroll? There's a high likelihood that there are several functions which run on-scroll which are causing reflows, a common problem which such code.
Once you've assessed your code, you can verify exactly what happens when the code executes using the "Performance" tab in Google Chrome to do this (Learn how to use the Performance Tab here). You can take the appropriate action.
Assuming that your code suffers from the same problem I've encountered in my formative years using jQuery - multiple or problematic on-scroll events - you can de-bounce the ones which can run after scrolling has completed. You can do this by using the following pattern.
Scenario 1:
This would run many times. 'N' times for each scrollwheel drag (dependent on settings - mine is 10) and even more times when using the scrollbar.
function someFunc() {
let someArr = [];
for(var i = 0; i < 1000000; i++ {
someArr.push((i * 2));
}
for(var i = 0; i < someArr.length; i++ {
someArr[i] /= 0.25;
}
}
$(window).on("scroll", function() {
someFunc();
});
Scenario 2:
This would run once after scrolling has finished. Waiting for 200ms before executing to ensure the user has completely finishing scrolling.
function someFunc() {
let someArr = [];
for(var i = 0; i < 1000000; i++ {
someArr.push((i * 2));
}
for(var i = 0; i < someArr.length; i++ {
someArr[i] /= 0.25;
}
}
let debouncedTimeout = null;
$(window).on("scroll", function() {
if (debouncedTimeout) {
clearTimeout(debouncedTimeout);
}
debouncedTimeout = setTimeout(someFunc(), 200);
});
Add a console.log("Check") with numbers (check1, check2) and so on in every file. Check your console in your browser, look in which series they load in console. If it takes a while to load the next log you know its the previous file with the number you logged.
Your console should say everything.
But loading so many js files is bad practice.
Try to fit everything in to one, so in further bckend development can go to an .min.js
But if you keep doing this. The simplest way is to keep track of the funcioncesequences with console.log, so that you evrything works how it is supposed to
:)

Kill all setInterval in Angular

How can I find and stop all of setInterval (s) in Angular?
Some setInterval do not created by my program and run in another components or even a jQuery in my web application and I want to stop it!
CORS is not concerned.
If you absolutely need "a" solution, even a bad one, then you can overwrite the setInterval and setTimeout functions, and maintain your own list of assigned interval identifiers. However, a much better solution would be to write your code to deal with the fact that other parts of the libraries and frameworks you use can schedule timeouts and ntervals, instead of forcefully killing them with complete disregard of what was relying on them to run in the first place.
Indiscriminantly killing all timers will break things.
So, with that covered, the terrible solution would be code like this:
(function monitorIntervalsAndTimeouts() {
const timerList = [];
const oldSetInterval = globalThis.setInterval.bind(globalThis);
globalThis.setInterval = function(...args) {
const timerId = oldSetInterval(...args);
timerList.push(timerId);
return timerId;
};
const oldSetTimeout = globalThis.setTimeout.bind(globalThis);
globalThis.setTimeout = function(...args) {
const timerId = oldSetTimeout (...args);
timerList.push(timerId);
return timerId;
};
...
And of course, the corresponding clear functions:
...
const oldClearInterval = globalThis.clearInterval.bind(globalThis);
globalThis.clearInterval = function(timerId) {
const pos = timerList.indexOf(timerId);
if (pos > -1) timerList.splice(pos, 1);
return oldClearInterval(timerId);
};
const oldClearTimeout = globalThis.clearTimeout.bind(globalThis);
globalThis.clearTimeout = function(timerId) {
const pos = timerList.indexOf(timerId);
if (pos > -1) timerList.splice(pos, 1);
return oldClearTimeout(timerId);
};
...
and finally, a "clear all timers" function:
...
// Call this whatever you want.
globalThis.clearAllIntervalsAndTimeouts = function() {
while(timerList.length) {
// clearInterval and clearTimeout are, per the HTML standard,
// the exact same function, just with two names to keep code clean.
oldClearInterval(timerList.shift());
}
};
})();
You then make sure you load this using <script src="./monitor-intervals-and-timeouts.js"></script>, without the async and/or defer keyword, as very first script in your page's <head> element, so that it loads before anything else. And yes: that will block the page parsing while it does that. Or if you're using Node (which this will work fine for, too, because of the use of globalThis), you need to make sure it's the absolute first thing your main entry point imports/requires (depending on whether you're using ESM or CJS code).
Remember: this is the nuclear option. If you use this code, you're using it because you need to run something for local dev work that lets you debug your project, for finding out where timeouts/intervals are getting set and cleared. If you need it for that: go for it. But never, ever use this in production to solve a problem relating to timeouts/intervals used by third party/vendor code. Solve that the right way by actually solving whatever problem your own code has.

How to check memory leakage of javascript running as devtool

There are lots of information regarding how to check javascript memory running inside certain page, but I need to check the memory usage of my javascript inputted via console.
I am developing some scripts to work as robot like game assistant, so I put my script under the thread(frame) and triggered by setInterval calls every second. Sample code like following:
var msgList = [];
var msgGenerator = function () {
return "Welcome";
}
var msgInterval = setInterval(function () {
var msg = msgGenerator();
if (!msgList.includes(msg))
msgList.push(msg);
}, 1 * 1000);
Actually the message is very small and in total it will be less than 50. But the memory of such devtool(viewed from task manager of chrome) is increasing.
Even after I execute the following call:
clearInterval(msgInterval);
The memeory of devtool is still increasing.
Can anyone guide me how to get memory snapshot of devtool(javascript running on console)? Or is there any other way to analyze the memory leakage of homemade javascript running on console?
I have tried many ways to get the detail memory usage of devtool but failed. It seems there is no such tool/facility. Anyway, if someone found such tool later, please post in this thread.
However, I find the way to avoid memory usage increase of my script running on console: change the setInterval to setTimeout, and achieve the same effect.
The updated codes as following will not have any memory usage increasing any more.
var msgList = [];
var msgGenerator = function () {
return "Welcome";
}
var msgInterval = 0;
var msgLoop = function () {
var msg = msgGenerator();
if (!msgList.includes(msg))
msgList.push(msg);
msgInterval = setTimeout(msgLoop, 1 * 1000);
};
Hope above message can help someone is struggling to solve the "memory leakage" of console scripts or snippets.

Javascript inner function memory leak?

I've been working with Javascript recently but I'm not actually very proficient with it. I'm trying to track down what looks to be a memory leak, since my Memory Usage seems to steadily increase. One idea I have for what might be causing this is the use of inner functions. I use inner functions when dealing with XMLHTTPRequests (and other related objects) and these requests get created every few seconds. I feel like perhaps I'm not closing it properly or something. So here's some code for one of the inner functions, if someone could tell me if this is the problem and how to fix it that would be great. (This is in the context of IE, I do not know and cannot find out if it happens in other browsers).
me.request.open("GET", url, true);
me.request.onreadystatechange = onReadyStateChange;
me.request.send();
function onReadyStateChange() {
if (4 == me.request.readyState) {
if (me.request.status == 200) {
var results = me.request.responseText;
var resultsString = me.request.responseText.toString();
me.stringOperation(resultsString);
me.request.abort();
me.request = null;
} else {
me.request.abort();
me.request = null;
}
}
}
Add
function noop() {}
And then in onReadyStateChange, after me.request = null, me.request.onreadystatechange = noop.
This removes a cyclic reference between me, and onReadyStateChange that crosses the JS object / host object barrier.

using javascript to track another javascript script?

I was just wondering whether there are any way (libraries, frameworks, tutorials) to do javascript tracking with another script? Basically, i want to track as the user work with the site, which function gets executed with what parameters and so on, as detailed as possible.
thanks a lot!
The extent of detail you're expecting will be challenging for any solution to gather and report on without severely slowing down your scripts -- consider that, for every call, at least 1 other call would need to occur to gather this.
You'd be better to pick a few key events (mouse clicks, etc.) and track only a few details (such as time) for them. If you're using ajax, keep JavaScript and the browser oblivious and just track this on server-side.
There's a few options but I'm not sure if there are any "great" ones. I take it Firebug/IE Dev toolbar profiling won't work because you are trying to track remote user's actions.
So, one option (I'm not highly recommending for production purposes), will work in some but not all browsers.
Essentially you overwrite every function, with a wrapper that you then inject your logging.
(I haven't tested this, trying to recall it from memory... hopefully in "pseudo code" you get the idea...)
//e.g. get all functions defined on the global window object
function logAll(){
var funcs = [];
var oldFunc;
for(var i in window){
try {
if(typeof(window[i]) == 'function'){
if(i != 'logAll'){
funcs.push(i);
}
}
} catch(ex){
//handle as desired
}
}
var x;
for(var i in funcs){
x = '_' + new Date().getTime();
window[x] = window[i];//save the old function as new function
//redefine original
window[i] = function(){
//do your logging here...
//then call the real function (and pass all params along)
call(window[x]);
};
}
};

Categories