Delay logic for web automation chrome extension (Javascript) - javascript

I am making a chrome extension for web automation.The first step is to get a list of sites and instructions from a server in a delimiter-format.
Once the list is obtained it is divided into an array that i call "siteArray".
The site array is then divide into another array i call "instructionsArray"
and among those items in the array one of them is the duration spent on the site i.e instructionsArray[5] has the value of "10" seconds. {the duration is not the same for all the sites}
My question arises in how to delay(implementing duration)
One implementation I found is using a sleep function which turns out to be inefficient as it is just a long for loop
see code:
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}
and then:
getlist();
for each(siteArray){
instructionsArray = siteArray[count].split(delimiter);
gotoUrl(instructionsArray[1]);
sleep(instructionsArray[5]);
count++;
}
Where
getlist() fetches a list of instructions and splits into the
siteArray .
gotoUrl() changes url.
sleep() runs the sleep function.
Is there a better way to implement a duration/wait/delay.

Rework the way you iterate over the array: process one entry, set a timer, repeat.
var count = 0;
processSite();
function processSite() {
if (count >= siteArray.length) {
console.log('all done!');
return;
}
var instructionsArray = siteArray[count].split(delimiter);
count++;
gotoUrl(instructionsArray[1]);
setTimeout(processSite, instructionsArray[5]);
}

After giving much thought into it I have solved the issue.
Using wOxxOm's idea I came up with this 'sub-idea' to create multiple timeouts i.e
for(index = 1;index<(siteArray.length);index++){
instructionsArray = siteArray[index].split(delimiter);
bigtime+=instructionsArray[5];
setTimeout(function(){processNext(count)},bigtime);
}
This code creates multiple timeout functions for each site.
similar to :
setTimeout(function(){processNext(count)},10 sec);
setTimeout(function(){processNext(count)},10+10 sec);
setTimeout(function(){processNext(count)},10+10+10 sec);
but in a dynamic way.
The count variable is change in the processNext function:
processNext(count){
instructionsArray = siteArray[count].split(delimiter);
visitUrl(instructionsArray[1]);
count++;
}
I give Thanks to wOxxOm for the insight.

Related

Why doesn't setTimeout() work in my sorting function?

UPDATE
I could not figure out what the problem was to this, but I found a solution to my overall problem. I simply changed my bubbleSort() algorithm to only utilize for-loops and that appeared to work. Thank you for the help!
I am currently learning javaScript and React. As a practice project, I am attempting to create a sorting algorithm visualizer. For a (what I thought would be a simple) warmup, I have implemented bubble sort first, using a psuedocode I found online. I had it so the array sorts and changes on screen, but it was too fast so it didn't show the actual animation for the process on how it got to that point. In order to do that I would need to delay each iteration of the loops in the algorithm. However, my implementation is acting strange. The display animates the first iteration being sorted, then suddenly stops. The algorithm seems to quit early instead of finish the whole process when I add setTimeout() to my function. To finish the whole sorting process you need to repeatedly press the button until every single item is sorted. Nothing I try seems to work, and I would be very grateful if anyone could explain why and maybe offer some help.
bubbleSort() {
var arr = this.state.array
var isSorted = false
var lastUnsorted = arr.length - 1
while (!isSorted) {
isSorted = true;
for (let i = 0; i < lastUnsorted; ++i) {
setTimeout(() => {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1)
isSorted = false
}
this.setState({ array: arr })
}, i * 1);
}
lastUnsorted--
}
this.setState({ array: arr })
}
setTimeout is asynchronous, but your code doesn't wait for it to resolve, so it calls setTimeout for lastUnsorted times, so all the timeouts happen at the same time.
You need to controll it with callbacks.
The setup below will cause the console.log('tick') to happen every 1 second for 5 seconds.
const functionToRepeat = () => {
console.log('tick')
}
const loopWithCallback = (callback, i, limit) => {
if(i > limit) return
setTimeout(()=>{
callback(loopWithCallback(callback, i+1, limit))
}, 1000)
}
loopWithCallback(functionToRepeat, 1, 5)

Google Sheets Custom Function Timing Out

So I have been having erratically getting an error: "Internal Error Executing the Custom Function" when running my custom function on a decently sized range of cells.
Google specifies "A custom function call must return within 30 seconds. If it does not, the cell will display an error: Internal error executing the custom function."
My custom function is this:
function ConcatLoop(rangeString, concatString, isPrefix, isOneColumn) {
//var rangeString = "A1:A10,B1:B10,C1:C10";
//var concatString = "1x ";
//var isPrefix = "true";
//var isOneColumn = "true";
var rangeStringArray = rangeString.split(',');
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var rangeValues=[];
//GRABBING THE VALUES FROM EACH RANGE
for(i=0;i<rangeStringArray.length;i++)
{
var range = sheet.getRange(rangeStringArray[i]);
rangeValues.push(range.getValues());
}
if(isOneColumn){var newRangeValues = [[]]};
//REMOVES EMPTY STRINGS AND ARRAYS OR CONCATENATES THE STRINGS
for (i = 0; i < rangeValues.length; i++) {
for (j = 0; j < rangeValues[i].length; j++){
if (rangeValues[i][j] == "")
{
rangeValues[i].splice(j, 1);
j--;
}
else if(isPrefix == "true")
{
rangeValues[i][j] = concatString + rangeValues[i][j];
if(isOneColumn){newRangeValues[0].push(rangeValues[i][j])};
}
else
{
rangeValues[i][j] = rangeValues[i][j] + concatString;
if(isOneColumn){newRangeValues[0].push(rangeValues[i][j])};
}
}
if (rangeValues[i] ==""){
rangeValues.splice(i,1);
i--;
}
}
//LOG WHILE TESTING
//if(isOneColumn){Logger.log(JSON.stringify(newRangeValues))}
//else{Logger.log("range values after concat: " + rangeValues)}
//RETURN WHILE RUNNING
if(isOneColumn){return newRangeValues}
else{return rangeValues};
}
When I have 1000 values plugged into the function, it takes quite a while to pull all the values. It runs fine when I test it in GOogle Scripts, because there isn't a time constraint there.
If there is anything I can do to work around this or make this more efficient, could someone let me know? Thank you so much!
Thanks to the incredible individuals who commented, I did find an answer earlier, so I'm going to post it for anyone who might need it.
Google sets a limitation of 30 seconds for the execution time of any custom function on a spreadsheet. Anything above that will give you an internal error. Although there is a limitation of 5 minutes for the execution time in the Script Editor, there is probably an issue if your script takes over 30 seconds to execute. In my case, I had too many separate ranges I needed to pull data out of. So I was calling "getValues(range)" quite a few times within a for-loop.
The solution was to pull each entire sheet in the spreadsheet as one range. So rather than having 7 ranges for each of my 27 sheets, I had 1 range for each of my 27 sheets. This caused me to have an excess amount of unnecessary information in memory, but also caused the execution time to go from around 45 seconds to 10 seconds. Solved my problem entirely.
Thank you!

how to work with a large array in javascript [duplicate]

This question already has answers here:
Best way to iterate over an array without blocking the UI
(4 answers)
Closed 6 years ago.
In my application I have a very big array (arround 60k records). Using a for loop I am doing some operations on it as shown below.
var allPoints = [];
for (var i = 0, cLength = this._clusterData.length; i < cLength; i+=1) {
if (allPoints.indexOf(this._clusterData[i].attributes.PropertyAddress) == -1) {
allPoints.push(this._clusterData[i].attributes.PropertyAddress);
this._DistClusterData.push(this._clusterData[i])
}
}
When I run this loop the browser hangs as it is very big & in Firefox is shows popup saying "A script on this page may be busy, or it may have stopped responding. You can stop the script now, or you can continue to see if the script will complete". What can I do so that browser do not hang?
You need to return control back to the browser in order to keep it responsive. That means you need to use setTimeout to end your current processing and schedule it for resumption sometime later. E.g.:
function processData(i) {
var data = clusterData[i];
...
if (i < clusterData.length) {
setTimeout(processData, 0, i + 1);
}
}
processData(0);
This would be the simplest thing to do from where you currently are.
Alternatively, if it fits what you want to do, Web Workers would be a great solution, since they actually shunt the work into a separate thread.
Having said this, what you're currently doing is extremely inefficient. You push values into an array, and consequently keep checking the ever longer array over and over for the values it contains. You should be using object keys for the purpose of de-duplication instead:
var allPoints = {};
// for (...) ...
if (!allPoints[address]) { // you can even omit this entirely
allPoints[address] = true;
}
// later:
allPoints = allPoints.keys();
First of all, avoid the multiple this._clusterData[i] calls. Extract it to a variable like so:
var allPoints = [];
var current;
for (var i = 0, cLength = this._clusterData.length; i < cLength; i+=1) {
current = this._clusterData[i];
if (allPoints.indexOf(current.attributes.PropertyAddress) == -1) {
allPoints.push(current.attributes.PropertyAddress);
this._DistClusterData.push(current)
}
}
This should boost your performance quite a bit :-)
As others already pointed out, you can do this asynchronously, so the browser remains responsive.
It should be noted however that the indexOf operation you do can become very costly. It would be better if you would create a Map keyed by the PropertyAddress value. That will take care of the duplicates.
(function (clusterData, batchSize, done) {
var myMap = new Map();
var i = 0;
(function nextBatch() {
for (data of clusterData.slice(i, i+batchSize)) {
myMap.set(data.attributes.PropertyAddress, data);
}
i += batchSize;
if (i < clusterData.length) {
setTimeout(nextBatch, 0);
} else {
done(myMap);
}
})();
})(this._clusterData, 1000, function (result) {
// All done
this._DistClusterData = result;
// continue here with other stuff you want to do with it.
}.bind(this));
Try considering adding to the array asynchronously with a list, for a set of 1000 records at a time, or for what provides the best performance. This should free up your application while a set of items is added to a list.
Here is some additional information: async and await while adding elements to List<T>

Appending Different Random Number To URL In Javascript Array On Each Loop

I'm trying (without much success) to create an array which contains slides being loaded into an iframe. One of these frames (/Events.php) uses PHP to query a WordPress database and show 1 post chosen at random. This slide needs to show a different random post every time the array loops through.
My code at them moment is...
<script type="text/javascript">
var frames = Array(
'http://www.example.com/Slide01.php', 5,
'http://www.example.com/Slide02.php', 5,
getRandomUrl(), 5,
'http://www.example.com/Slide04.php', 5
);
var i = 0, len = frames.length;
function getRandomUrl()
{
return "http://www.example.com/Events.php?=" + (new Date().getTime());
}
function ChangeSrc()
{
if (i >= len) { i = 0; } // start over
document.getElementById('myiframe').src = frames[i++];
setTimeout('ChangeSrc()', (frames[i++]*1000));
}
window.onload = ChangeSrc;
</script>
The only trouble is everytime /Events.php is shown it has the same number appended to it so therefore shows the same post in each loop.
I need to append a different number to the /Events.php slide on each loop so it generates different content each time.
I'm starting to think I'm approaching this in totally the wrong way so any help or pointers in the right direction would be appreciated!
Cheers,
Mark.
The issue is you are only calling getRandomUrl() once which is when you defined your array, this means the value will always be the same as its only returned once.
One solution would be to store the function itself in your array like so:
var frames = Array(
'http://www.example.com/Slide01.php', 5,
'http://www.example.com/Slide02.php', 5,
getRandomUrl, 5,
'http://www.example.com/Slide04.php', 5
);
And then call it in ChangeSrc() if its a function
function ChangeSrc()
{
if (i >= len) { i = 0; } // start over
var frame = frames[i++],
isFnc = typeof(frame) == "function";
if(isFnc){
frame = frame();
}
document.getElementById('myiframe').src = frame;
setTimeout(function(){
ChangeSrc()
}, frames[i++]*1000);
}
http://jsfiddle.net/redgg6pq/
A tip would be that you are only calling 'getRandomUrl' once, hence why it's always the same image. You want to call it each time you are in the loop.
I would suggest removing it from the static array, and calling it in the loop - does that make sense? :)
HTH

JavaScript Automated Clicking

So, here's my issue.
I need to write a script to be run in the console (or via Greasemonkey) to automate clicking of certain links to check their output.
Each time one of these links is clicked, they essentially generate an image in a flash container to the left. The goal here is to be able to automate this so that the QC technicians do not have to click each of these thumbnails themselves.
Needless to say, there needs to be a delay between each "click" event and the next so that the user can view the large image and make sure it is okay.
Here is my script thus far:
function pausecomp(ms) {
ms = ms + new Date().getTime();
while (new Date() < ms){}
}
var itemlist, totalnumber, i;
itemlist = document.getElementsByClassName("image");
totalnumber = parseInt(document.getElementById("quickNavImage").childNodes[3].firstChild.firstChild.nodeValue.replace(/[0-9]* of /, ""));
for(i = 0; i < totalnumber; i = i + 1) {
console.log(i);
itemlist[i].childNodes[1].click();
pausecomp(3000);
}
Now, totalnumber gets me the total number of thumbnails, obviously, and then itemlist is a list of get-able elements so I can access the link itself.
If I run itemlist[0].childNodes[1].click() it works just fine. Same with 1, 2, 3, etc. However, in the loop, it does nothing and it simply crashes both Firefox and IE. I don't need cross-browser capability, but I'm confused.
There is a built-in JS function "setInterval(afunction, interval)" that keeps executing a given function every "interval" miliseconds (1000 = 1s).
This fiddle shows how to use setTimeout to work through an array. Here is the code:
var my_array = ["a", "b", "c", "d"];
function step(index) {
console.log("value of my_array at " + index + ":", my_array[index]);
if (index < my_array.length - 1)
setTimeout(step, 3000, index + 1);
}
setTimeout(step, 3000, 0);
Every 3 seconds, you'll see on the console something like:
value of my_array at x: v
where x is the index in the array and v is the corresponding value.
The problem with your code is that your pausecomp loop is a form of busy waiting. Let's suppose you have 10 items to go through. Your code will click an item, spin for 3 seconds, click an item, spin for 3 seconds, etc. All your clicks are doing is queuing events to be dispatched. However, these events are not dispatched until your code finishes executing. It finishes executing after all the clicks are queued and (roughly) 30 seconds (in this hypothetical scenario) have elapsed. If the number of elements is greater that's even worse.
Using setTimeout like above allows the JavaScript virtual machine to regain control and allows dispatching events. The documentation on setTimeout is available here.
People were correct with SetInterval.
For the record, here's the completed code:
/*global console, document, clearInterval, setInterval*/
var itemlist, totalnumber, i, counter;
i = 0;
function findmepeterpan() {
"use strict";
console.log("Currently viewing " + (i + 1));
itemlist[i].scrollIntoView(true);
document.getElementById("headline").scrollIntoView(true);
itemlist[i].style.borderColor = "red";
itemlist[i].style.borderWidth = "thick";
itemlist[i].childNodes[1].click();
i = i + 1;
if (i === totalnumber) {
clearInterval(counter);
console.log("And we're done! Hope you enjoyed it!");
}
}
function keepitup() {
"use strict";
if (i !== 0) {
itemlist[i - 1].style.borderColor = "transparent";
itemlist[i - 1].style.borderWidth = "medium";
}
findmepeterpan();
}
itemlist = document.getElementsByClassName("image");
totalnumber = parseInt(document.getElementById("quickNavImage").childNodes[3].firstChild.firstChild.nodeValue.replace(/[0-9]* of /, ""), 10);
counter = setInterval(keepitup, 1500);

Categories

Resources