javascript function move to webworker - javascript

I have function:
function addModel() {
var values = new Array();
var $input = $('input[type=\'text\']');
var error = 0;
$input.each(function() {
$(this).removeClass('double-error');
var that = this;
if (that.value!='') {
values[that.value] = 0;
$('input[type=\'text\']').each(function() {
if (this.value == that.value) {
values[that.value]++;
}
});
}
});
$input.each(function(key) {
if (values[this.value]>1) {
//error++;
var name = this.value;
var product_model = $(this).parent().parent().find('.product-model').text();
var m = product_model.toLowerCase().areplace(search,replace);
$(this).parent().find('input[type=\'text\']').val(name + '-' + m);
}
});
return error <= 0; //return error > 0 ? false : true;
}
where are a lot of inputs to recheck... up to 50000. Usually are about 5000 to 20000 inputs. Of course browsers are freezing... How to move this function to web-worker and call it to get data back and fill form type="text"
thank you in advance.

Web workers don't have direct access to the DOM. So to do this, you'd need to gather the values of all 5-50k inputs into an array or similar, and then send that to the web worker (via postMessage) for processing, and have the web worker do the relevant processing and post the result back, and then use that result to update the inputs. See any web worker tutorial for the details of launching the worker and passing messages back and forth (and/or see my answer here).
Even just gathering up the values of the inputs and posting them to the web worker is going to take significant time on the main UI thread, though, as is accepting the result from the worker and updating the inputs; even 5k inputs is just far, far too many for a web page.

If maintaining browser responsiveness is the issue, then releasing the main thread periodically will allow the browser to process user input and DOM events. The key to this approach is to find ways to process the inputs in smaller batches. For example:
var INTERVAL = 10; // ms
var intervalId = setInterval((function () {
var values = [];
var $input = $('input[type=\'text\']');
var index;
return function () {
var $i = $input[index];
var el = $i.get();
$i.removeClass('double-error');
if (el.value != '') {
values[el.value] = 0;
$input.each(function() {
if (this.value == el.value) {
values[el.value]++;
}
});
}
if (index++ > $input.length) {
clearInterval(intervalId);
index = 0;
// Now set elements
intervalId = setInterval(function () {
var $i = $input[index];
var el = $i.get();
if (values[el.value] > 1) {
var name = el.value;
var product_model = $i.parent().parent().find('.product-model').text();
var m = product_model.toLowerCase().areplace(search,replace);
$i.parent().find('input[type=\'text\']').val(name + '-' + m);
}
if (index++ > $input.length) {
clearInterval(intervalId);
}
}, INTERVAL);
}
};
}()), INTERVAL);
Here, we do just a little bit of work, then use setInterval to release the main thread so that other work can be performed. After INTERVAL we will do some more work, until we finish and call clearInterval

Related

How to change value of global var when it is being used as a parameter in a function? (Javascript)

I want to be able to change the value of a global variable when it is being used by a function as a parameter.
My javascript:
function playAudio(audioFile, canPlay) {
if (canPlay < 2 && audioFile.paused) {
canPlay = canPlay + 1;
audioFile.play();
} else {
if (canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
};
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
btnPitch01.addEventListener("click", function() {
playAudio(audioFilePitch01, canPlayPitch01);
});
My HTML:
<body>
<button id="btnPitch01">Play Pitch01</button>
<button id="btnPitch02">Play Pitch02</button>
<script src="js/js-master.js"></script>
</body>
My scenario:
I'm building a Musical Aptitude Test for personal use that won't be hosted online. There are going to be hundreds of buttons each corresponding to their own audio files. Each audio file may only be played twice and no more than that. Buttons may not be pressed while their corresponding audio files are already playing.
All of that was working completely fine, until I optimised the function to use parameters. I know this would be good to avoid copy-pasting the same function hundreds of times, but it has broken the solution I used to prevent the audio from being played more than once. The "canPlayPitch01" variable, when it is being used as a parameter, no longer gets incremented, and therefore makes the [if (canPlay < 2)] useless.
How would I go about solving this? Even if it is bad coding practise, I would prefer to keep using the method I'm currently using, because I think it is a very logical one.
I'm a beginner and know very little, so please forgive any mistakes or poor coding practises. I welcome corrections and tips.
Thank you very much!
It's not possible, since variables are passed by value, not by reference. You should return the new value, and the caller should assign it to the variable.
function playAudio(audioFile, canPlay) {
if (canPlay < 2 && audioFile.paused) {
canPlay = canPlay + 1;
audioFile.play();
} else {
if (canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
return canPlay;
};
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
btnPitch01.addEventListener("click", function() {
canPlayPitch01 = playAudio(audioFilePitch01, canPlayPitch01);
});
A little improvement of the data will fix the stated problem and probably have quite a few side benefits elsewhere in the code.
Your data looks like this:
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
// and, judging by the naming used, there's probably more like this:
const btnPitch02 = document.getElementById("btnPitch02");
const audioFilePitch02 = new Audio("../aud/Pitch02.wav");
var canPlayPitch02 = 0;
// and so on
Now consider that global data looking like this:
const model = {
btnPitch01: {
canPlay: 0,
el: document.getElementById("btnPitch01"),
audioFile: new Audio("../aud/Pitch01.wav")
},
btnPitch02: { /* and so on */ }
}
Your event listener(s) can say:
btnPitch01.addEventListener("click", function(event) {
// notice how (if this is all that's done here) we can shrink this even further later
playAudio(event);
});
And your playAudio function can have a side-effect on the data:
function playAudio(event) {
// here's how we get from the button to the model item
const item = model[event.target.id];
if (item.canPlay < 2 && item.audioFile.paused) {
item.canPlay++;
item.audioFile.play();
} else {
if (item.canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
};
Side note: the model can probably be built in code...
// you can automate this even more using String padStart() on 1,2,3...
const baseIds = [ '01', '02', ... ];
const model = Object.fromEntries(
baseIds.map(baseId => {
const id = `btnPitch${baseId}`;
const value = {
canPlay: 0,
el: document.getElementById(id),
audioFile: new Audio(`../aud/Pitch${baseId}.wav`)
}
return [id, value];
})
);
// you can build the event listeners in a loop, too
// (or in the loop above)
Object.values(model).forEach(value => {
value.el.addEventListener("click", playAudio)
})
below is an example of the function.
btnPitch01.addEventListener("click", function() {
if ( this.dataset.numberOfPlays >= this.dataset.allowedNumberOfPlays ) return;
playAudio(audioFilePitch01, canPlayPitch01);
this.dataset.numberOfPlays++;
});
you would want to select all of your buttons and assign this to them after your html is loaded.
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
const listOfButtons = document.getElementsByClassName('pitchButton');
listOfButtons.forEach( item => {
item.addEventListener("click", () => {
if ( this.dataset.numberOfPlays >= this.dataset.allowedNumberOfPlays ) return;
playAudio("audioFilePitch" + this.id);
this.dataset.numberOfPlays++;
});

Change the cells of a table in an interval time using OData model

I have this code and I need my table to show the first 10 patients and, after 10 seconds, show the next 10 without touching any button (automatically).
I'm looking for something similar to this: https://embed.plnkr.co/ioh85m5OtPmcvPHyl3Bg/
But with an OData model (as specified on my view and controller).
This is my view:
<Table id="tablaPacientes" items="{/EspCoSet}">
<columns>
<!-- ... -->
</columns>
<ColumnListItem>
<ObjectIdentifier title="{Bett}" />
<!-- ... -->
</ColumnListItem>
</Table>
This is my controller:
onInit: function () {
var oModel = this.getOwnerComponent().getModel("zctv");
this.getView().setModel(oModel);
},
onBeforeRendering: function () { // method to get the local IP because I need it for the OData
var ipAddress;
var RTCPeerConnection = window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
var self = this;
function grepSDP (sdp) {
var ip = /(192\.168\.(0|\d{0,3})\.(0|\d{0,3}))/i;
sdp.split('\r\n').forEach(function (line) {
if (line.match(ip)) {
ipAddress = line.match(ip)[0];
self.setIp(ipAddress);
}
});
}
if (RTCPeerConnection) {
(function () {
var rtc = new RTCPeerConnection({
iceServers: []
});
rtc.createDataChannel('', {
reliable: false
});
rtc.onicecandidate = function (evt) {
if (evt.candidate) {
grepSDP(evt.candidate.candidate);
}
};
rtc.createOffer(function (offerDesc) {
rtc.setLocalDescription(offerDesc);
}, function (e) {
console.log("Failed to get Ip address");
});
})();
}
},
setIp: function (ip) {
this.getView().byId("planta").bindElement({
path: "/CenTVSet('" + ip + "')"
});
var oModel = this.getView().getModel();
var that = this;
oModel.read("/CenTVSet('" + ip + "')", {
success: function (oData, oRes) {
var einri = oData.Einri;
var orgpf = oData.Orgpf;
var oTable = that.getView().byId("tablaPacientes");
var oBinding = oTable.getBinding("items");
var aFilters = [];
var filterO = new Filter("Orgna", sap.ui.model.FilterOperator.EQ, orgpf);
aFilters.push(filterO);
var filterE = new Filter("Einri", sap.ui.model.FilterOperator.EQ, einri);
aFilters.push(filterE);
oBinding.filter(aFilters);
}
});
}
I searched some functions like IntervalTrigger but I really don't know how can I use it for this example.
Here are some small samples:
OData V4: https://embed.plnkr.co/4zIAH7q2E0lngbyX
OData V2: https://embed.plnkr.co/rNa0TktXiQqSCGJV
startList: function(listBase, $skip, $top, restInfo) {
let startIndex = $skip;
let length = $top;
let totalSize;
(function repeat(that) {
const bindingInfo = Object.assign({ startIndex, length }, restInfo);
listBase.bindItems(bindingInfo);
listBase.data("repeater", event => {
totalSize = event.getParameter("total"); // $count value
startIndex += $top;
startIndex = startIndex < totalSize ? startIndex : 0;
setTimeout(() => repeat(that), 2000);
}).attachEventOnce("updateFinished", listBase.data("repeater"), that);
})(this);
},
stopList: function(listBase) {
listBase.detachEvent("updateFinished", listBase.data("repeater"), this);
},
The samples make use of startIndex and length in the list binding info which translates to $skip and $top system queries of the entity request URL. I.e. appending those system queries to the request URL (e.g. https://<host>/<service>/<EntitySet>?$skip=3&$top=3), should return the correct set of entities like this.
Additional options for the list binding info can be found in the UI5 documentation as I explained here.
JavaScript part
The interval is implemented with an IIFE (Immediately Invoked Function Expression) in combination with setTimeout instead of setInterval.
setInterval has the following disadvantages:
The callback is not immediately invoked. You'd have to wait 10 seconds first to trigger the 1st callback.
Does not wait for the data response to arrive. This may cause skipping a batch or showing it for a too short period of time because the delay simply continues regardless of the server response.
setTimeout instead offers a better control when the next batch should be requested.
You could bind you items using bindItems, pass skip,top parameters and wrap the whole thing in a setInterval
var iSkip = 0;
var iTop = 10;
setInterval(function() {
table.bindItems("/EspCoSet", {
urlParameters: {
"$skip": iSkip.toString() // Get first 10 entries
"$top": iTop.toString()
},
success: fuction (oData) {
iSkip = iTop; // Update iSkip and iTop to get the next set
iTop+= 10;
}
...
}, 10000); // Each 10 seconds
)
Almost the same thing, just use oModel.read to read the entities into you viewModel.allEntities, bind your table to the viewModel.shownEntities and use a setInterval to get the next 10 from allEntities to update shownEntities.

How to wait for multiple WebWorkers in a loop

I have the following issue with Web Workers in JS. I have a heavy duty application doing some simulation. The code runs in multiple Web Workers.
The main thread is running on a WebPage. But could also be a Web Worker, if it makes sense.
Example:
var myWebWorkers = [];
function openWorker(workerCount){
for(var i = 0; i < workerCount; i++){
myWebWorkers[i] = new Worker('worker.js');
myWebWorkers[i].onmessage = function(e){
this.result = e.data;
this.isReady = true;
}
}
}
function setWorkerData(somedata){
// somedata.length is always a multiple of myWebWorkers.length
var elementCntPerWorker = somedata.length / myWebWorkers.length;
myWebWorkers.forEach(function(worker, index){
worker.isReady = false;
worker.postMessage(
somedata.slice(index * elementCntPerWorker,
(index + 1) * elementCntPerWorker - 1));
});
}
var somedata = [...];
openWorker(8);
for(var i = 0; i < 10000; i++){
setWorkerData(somedata);
waitUntilWorkersAreDoneButAllowBrowserToReact(myWebWorkers);
if(x % 100) updateSVGonWebPage
}
function waitUntilWorkersAreDoneButAllowBrowserToReact(){
/* wait for all myWebWorkers-onchange event, but
allow browser to react and don't block a full Web Worker
Following example is my intension. But will not work, because
events are not executed until code excution stops.
*/
somedata = [];
for(var i = 0; i < myWebWorkers.length; i++){
while(!myWebWorkers[i].isReady);
somedata = somedata.concat(myWebWorkers.result);
}
}
What I need is really the waitUntilWorkersAreDoneButAllowBrowserToReact function or a concept to get this running. Every searching reagarding Mutex, sleep, etc ends in the following sentences: "JS is single threaded", "This will only work if you are not in a loop", "There is no reason to have a sleep function". etc.
Even when passing the main task to another Worker, I got the problem, that this thread is 100 % duty on checking, if the others are ready, which is waste of energy and processing power.
I would love to have a blocking function like myWebWorker.waitForReady(), which would allow events still to be handled. This would bring javascript to its next level. But may be I missed a simple concept that will do exactly this.
Thank you!
I would love to have a blocking function like myWebWorker.waitForReady()
No, that's not possible. All the statements you researched are correct, web workers stay asynchronous and will only communicate by messages. There is no waiting for events, not even on worker threads.
You will want to use promises for this:
function createWorkers(workerCount, src) {
var workers = new Array(workerCount);
for (var i = 0; i < workerCount; i++) {
workers[i] = new Worker(src);
}
return workers;
}
function doWork(worker, data) {
return new Promise(function(resolve, reject) {
worker.onmessage = resolve;
worker.postMessage(data);
});
}
function doDistributedWork(workers, data) {
// data size is always a multiple of the number of workers
var elementsPerWorker = data.length / workers.length;
return Promise.all(workers.map(function(worker, index) {
var start = index * elementsPerWorker;
return doWork(worker, data.slice(start, start+elementsPerWorker));
}));
}
var myWebWorkers = createWorkers(8, 'worker.js');
var somedata = [...];
function step(i) {
if (i <= 0)
return Promise.resolve("done!");
return doDistributedWork(myWebWorkers, somedata)
.then(function(results) {
if (i % 100)
updateSVGonWebPage();
return step(i-1)
});
}
step(1000).then(console.log);
Promise.all does the magic of waiting for concurrently running results, and the step function does the asynchronous looping using a recursive approach.

AngularJS page causes Chrome to use up huge tab memory in background

I have a, fairly simple, AngularJS page that simply uses a service to pull data from the backend and show it on screen using an ng-repeat.
During normal usage after 10mins the memory for the page stabilises at around 100mb. If I switch tabs so that this page is no longer focused - it is in the background it will balloon in memory up until 1gb and then itll crash.
I am using $interval to refresh the data and I read recently that Chrome throttles intervals and timeouts when a page is in the background. Shouldn't this prevent this from happening? Anything I can do? As a last resort I have considered switching to requestAnimationFrame as I know this won't get called when the page is in the background.
$interval($scope.update, $scope.refreshInterval);
//
//
//
$scope.update = function () {
$scope.inError = false;
scheduleAppAPIservice.getSchedules().then(
function (response) {
$scope.schedules = null;
$scope.schedules = response.data;
angular.forEach($scope.schedules, $scope.createNextExecutionLabel);
angular.forEach($scope.schedules, $scope.createTrclass);
angular.forEach($scope.tabs, $scope.assignSchedulesToTab);
$scope.loading = false;
},
function () {
$scope.inError = true;
//console.log("failed getSchedules");
});
};
//
//
//
$scope.createNextExecutionLabel = function (schedule) {
schedule.nextExecutionDate = moment(schedule.NextExecution);
schedule.endExecutionDate = moment(schedule.EndExecution);
if (schedule.nextExecutionDate.year() > $scope.finishedCutoffYear) {
schedule.nextExecutionLabel = "Never";
}
else if (schedule.IsCurrentlyExecuting) {
schedule.nextExecutionLabel = "Running";
if (!isNaN(schedule.CurrentProgress) && schedule.CurrentProgress != 0) {
schedule.nextExecutionLabel += " (" + schedule.CurrentProgress + "%)";
}
else {
schedule.nextExecutionLabel += "...";
}
}
else if (schedule.nextExecutionDate < moment()) {
schedule.nextExecutionLabel = "Overdue";
}
else {
schedule.nextExecutionLabel = $filter('emRelativeTime')(schedule.nextExecutionDate);
}
}
//
//
//
$scope.createTrclass = function (schedule) {
var trclass = "system";
if (schedule.IsDisabled) {
trclass = "disabled";
}
else if (schedule.IsUserSchedule) {
if (schedule.nextExecutionDate.year() > $scope.finishedCutoffYear && schedule.ExecuteOnEvent == 0)
trclass = "finished";
else
trclass = "active";
}
schedule.trclass = trclass;
};
//
//
//
$scope.assignSchedulesToTab = function (tab) {
tab.scheduleCount = $scope.schedules.filter(function (x) {
return x.trclass == tab.label.toLowerCase();
}).length;
};
I don't have any information about background tab throttling so, the theory in this answer may not be true.
$interval works, say, every 5 seconds.
But the successful promise inside the $interval may not be resolved in 5 seconds.
So if scheduleAppAPIservice.getSchedules() doesn't resolve in 5 seconds for some reason, an increased numbers of promises will consume memory on each $interval cycle.
You may cancel previous request when starting a new one on $interval, or you can use $timeout + manual invocation of the next request when promise completed to make sure there is nothing that consumes memory in an uncontrolled way.

Calling function from function WEIRD RESULTS

I was trying to show a text gradually on the screen (like marquee). e.g. H.. He.. Hell.. Hello. when I'm tracing it in debug in VS2010 it's working! but when it's actually running it shows the whole sentence at once.
I made a certain "delay" for about 3 seconds between each letter so it would suppose to take a while, but in reality it's shows everything immediately.
Who's the genius to solve this mystery? (please don't give me advices how to create the marquee effect, it's not the issue anymore. now it's just a WAR between me and javascript!) I'm assuming that it has to do with synchronization when calling function from function?
Thanks to whomever will help me get my sanity back.
you can download the code from here (VS project):
http://pcgroup.co.il/downloads/misc/function_from_function.zip
or view it here:
<body>
<script type="text/javascript">
//trying to display this source sentence letter by letter:
var source = "hi javascript why are you being such a pain";
var target = "";
var pos = 0;
var mayGoOn = false;
//this function calls another function which suppose to "build" the sentence increasing index using the global var pos (it's even working when following it in debug)
function textticker() {
if (pos < source.length) {
flash();
if (mayGoOn == true) {
pos++;
mayGoOn = false;
document.write(target);
textticker();
}
}
}
function flash() {
//I tried to put returns everywhere assuming that this may solve it probably one of them in not necessary but it doesn't solve it
if (mayGoOn == true) { return; }
while (true) {
var d = new Date();
if (d.getSeconds() % 3 == 0) {
//alert('this suppose to happen only in about every 3 seconds');
target = source.substring(0, pos);
mayGoOn = true;
return;
}
}
}
textticker();
</script>
You're obviously doing it wrong. Take a look at this.
var message = "Hello World!";
function print(msg, idx) {
if(!idx) {
idx = 0;
}
$('#hello').html(msg.substring(0, idx));
if(idx < msg.length) {
setTimeout(function() { print(msg, idx + 1) }, 200);
}
}
print(message);
Demo: http://jsbin.com/evehus

Categories