I have succeeded in cobbling together pieces of code that achieve my goal. However, I would like some advice from more advanced vanilla JS programmers on how I can go about reaching my goal in a better way.
To start, I want to introduce my problem. I have a piece of text on my website where a portion is designed to change every so often. For this, I am running through a loop of phrases. To run this loop continuously, I first call the loop, then I call it again with setInterval timed to start when the initial loop ends. Here is the code I've got, which works even if it isn't what could be considered quality code:
function loop(){
for (let i = 0; i < header_phrases.length; i++){
(function (i) {
setTimeout(function(){
header_txt.textContent = header_phrases[i];
}, 3000 * i);
})(i);
};
}
loop();
setInterval(loop, 21000);
Is there a better way to right this code for both performance and quality? Do I need to use async? If so, any material I can see to learn more? Thanks!
You can implement the same logic using recursion.
function recursify(phrases, index = 0) {
header_txt.textContent = phrases[index];
setTimeout(function () {
recursify(phrases, index < phrases.length - 1 ? index + 1 : 0);
}, 300)
}
recursify(header_phrases);
The function 'recursify' will call itself after 300 miliseconds, but everytime this function gets called, the value of index will be different.
If I understand your requirement correctly, you want top populate an element from an array of values.
A simple way to do this is:
doLoop();
function doLoop() {
var phraseNo=0;
setTimeout(next,21000);
next();
function next() {
header_txt.textContent = header_phrases[phraseNo++];
if(phraseNo>=header_phrases.length) phraseNo=0;
}
}
This simply puts the next() function on the queue and waits.
The call to next() before the function simply starts it off without waiting for the timeout.
this is assuming that header_txt and header_phrases are not global vars. using global vars isn't a good idea.
var repeatIn = 3000;
phraseUpdater();
function phraseUpdater() {
var updateCount = 0,
phrasesCount = header_phrases.length;
setHeader();
setTimeout(setHeader, repeatIn);
function setHeader() {
header_txt.textContent = header_phrases[updateCount++ % phrasesCount] || '';
}
}
Related
Basically this pulls a list of visible links (some have display:none because of a filter that I've done prior) and clicks individually on each one with a time delay, as each link takes about 2 seconds to process. However, sometimes the link does not process and gives an error message. When this condition applies, I want to stop this process as it will only fill the server with requests where everyone returns an error. As I usually do, I already tried to create an if (condition) { return; }, but I was not very successful with this particular function.
var x = 1,
myVar = "",
ModelA = true,
VarOne = $('#axws a.icon_x').filter(":visible");
for (i = 0; i < 100; i++) {
if (ModelA) {
$(VarOne).eq(i).each(function() {
var timeNow = time * x;
setTimeout(function(myVar) {
$(myVar).click();
}, timeNow, this);
++x;
});
}
}
Also, I just want to say that my javascript is not excellent, I learned it myself to automate some things that make my life easier (and I'm still learning). If anyone can help me and take the time to explain to me why it works that way, I'd be very grateful. I've been trying to find a solution for 4 hours and I couldn't get it to work. Thanks again.
Edit:
In response to the first comments, I already tried to use a break, but the break gives me an "Unsyntactic break" error. From what I understand from searching this forum, a break cannot be used with .each (I could be wrong).
I also tried with return, both false and true (just in case), but none of them worked, the loop catches the console.log I put before the return, but it doesn't stop the function.
I've also tried with an if else, so that it only clicks on the next one if it doesn't catch the condition, but the loop goes for the if (writing the console.log) and the else (clicking the link).
Unfortunately, it wasn't for lack of trying. I'm not really getting it.
Edit 2: (Solved)
I managed to solve it and I'll leave the answer here so that people who go through the same problem can solve it. Unfortunately I didn't find a solution to the problem here on the forum.
I think the problem with this was that I was using the this keyword which made the loop just being done in the setTimeout function.
setTimeout(function(myVar) {
$(myVar).click();
}, timeNow, this);
Basically I redid the loop so I don't have to use the this keyword:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
} //New function to set a time out
var ModelA = true;
var VarOne= $('#axws a.farm_icon_x').filter(":visible");
var nVarOne = $('#axws a.farm_icon_x').filter(":visible").length; //new var to set the exact i length to the exact number of links
async function load () { // wrap loop into an async function for sleep to work
for (let i = 0; i < nfarm; i++) {
if (ModelA) {
$(VarOne).eq(i).each(function() {
VarOne.eq(i).click();
console.log("Model A: " + i);
});
}
var time = (Math.random() * (5000 - 2000) + 2000).toFixed(0); //var to set sleep time
await sleep(time); // then the created Promise can be awaited
}
}
load(); //loop function
}
That said, all that remains is to put the condition for the loop to stop if { condition == true}. As the loop will repeat the for and continue where the previous promise was, I just had to do, for example, this:
(...) if (ModelA && !condition) { ... }
Let's say I have a function:
myFunc = function(number) {
console.log("Booyah! "+number);
}
And I want it to run on a set interval. Sounds like I should use setInterval, huh!
But what if I want to run multiple intervals of the same function, all starting at the exact same time?
setInterval(function(){
myFunc(1);
}, 500);
setInterval(function(){
myFunc(2);
}, 1000);
setInterval(function(){
myFunc(3);
}, 2000);
So that the first runs exactly twice in the time it takes the second to run once, and the same between the second and third.
How do you make sure that they all start at the same time so that they are in sync?
Good question, but in JS you can't. To have multiple functions in the same program execute at the same time you need multi-threading and some deep timing and thread handling skills. JS is single threaded. setInterval doesn't acutally run the function after the delay, rather after the delay it adds the function to the event stack to be run as soon as the processor can get to it. If the proc is busy with another operation, it will take longer than the delay period to actually run. Multiple intervals/timeouts are all adding calls to the same event stack, so they run in turn as the proc is available.
function Timer(funct, delayMs, times)
{
if(times==undefined)
{
times=-1;
}
if(delayMs==undefined)
{
delayMs=10;
}
this.funct=funct;
var times=times;
var timesCount=0;
var ticks = (delayMs/10)|0;
var count=0;
Timer.instances.push(this);
this.tick = function()
{
if(count>=ticks)
{
this.funct();
count=0;
if(times>-1)
{
timesCount++;
if(timesCount>=times)
{
this.stop();
}
}
}
count++;
};
this.stop=function()
{
var index = Timer.instances.indexOf(this);
Timer.instances.splice(index, 1);
};
}
Timer.instances=[];
Timer.ontick=function()
{
for(var i in Timer.instances)
{
Timer.instances[i].tick();
}
};
window.setInterval(Timer.ontick, 10);
And to use it:
function onTick()
{
window.alert('test');
}
function onTick2()
{
window.alert('test2');
}
var timer = new Timer(onTick, 2000,-1);
var timer = new Timer(onTick2, 16000,-1);
For a finite number of ticks, change the last parameter to a positive integer for number. I used -1 to indicate continuous running.
Ignore anyone who tells you that you can't. You can make it do just about any thing you like!
You can make something like this.
arr = Array();
arr[0] = "hi";
arr[1] = "bye";
setTimer0 = setInterval(function(id){
console.log(arr[id])
},1000,(0));
setTimer1 = setInterval(function(id){
console.log(arr[id]);
},500,(1));
Hope it helps!
JavaScript is single threaded. You can use html5 web worker or try using setTimeout recursively. Create multiple functions following this example:
var interval = setTimeout(appendDateToBody, 5000);
function appendDateToBody() {
document.body.appendChild(
document.createTextNode(new Date() + " "));
interval = setTimeout(appendDateToBody, 5000);
}
Read this article:
http://weblogs.asp.net/bleroy/archive/2009/05/14/setinterval-is-moderately-evil.aspx
You can use multiples of ticks inside functions, in the example below you can run one function every 0.1 sec, and another every 1 sec.
Obviously, the timing will go wrong if functions require longer times than the intervals you set. You might need to experiment with the values to make them work or tolerate the incorrect timing.
Set a variable to handle tick multiples
let tickDivider = -1
Increase the value of tick variable inside the faster function
const fastFunc = ()=> {
tickDivider += 1
console.log('fastFunciton')
}
Use a condition to on running the slower function
const slowFunc = ()=> {
if (!(tickDivider % 10)){
console.log('slowFunction')
}
}
Call both functions in a single one. The order is not important unless you set tickDivider to 0 (of any multiple of 10)
const updateAllFuncs = () => {
fastFunc()
slowFunc()
}
Set the interval to the frequency of the faster function
setInterval(updateAllFuncs, 100)
What I'm doing here is adding a speed attribute to the HTML elements. These speeds are passed as a parameter to setCounter(). I did this mainly to make the code easier to test and play with.
The function setCounter() is invoked inside a loop for every HTML element with class counter. This function sets a new setInterval in every execution.
The intervals seem to be working in sync.
const elements = document.querySelectorAll('.counter')
elements.forEach((el, i) => {
let speed = Number(elements[i].getAttribute('speed'))
setCounter(el, speed, 5000)
})
function setCounter(element, speed, elapse){
let count = 0
setInterval(() => {
count = (count >= elapse) ? elapse : count + speed
if(count === elapse) clearInterval()
element.innerHTML = count
}, 1)
}
Same Speeds
<p class="counter" speed='10'></p>
<p class="counter" speed='10'></p>
Different Speeds
<p class="counter" speed='3'></p>
<p class="counter" speed='5'></p>
I am trying to do a infinite loop, but it only works if I include an 'alert' on it. My code looks like this:
while( tocontinue ){
// Some code
alert('Accept to continue');
}
On this way, the user has to click to hide the alerts (for example, on Chrome), and then the loop continues correctly by itself. I need to implement this without any alert. I also tried this:
while( tocontinue ){
// Some code
tocontinue = false;
setTimeout(function(){tocontinue=true},500);
}
And with "window.setTimeout" too, and without the word "function(){}", but it doesn't work. I tried everything: some implementations on JavaScript of a sleep() function, calling the function each X time with setInterval, answers 1 and 3 on this post... :/
Thank you very much for your time.
I'm trying to implement a genetic algorithm, and I want to stop it when I decide (with a button that puts the global variable "tocontinue" to false). Meanwhile, I want a infinite loop.
Well, you won't be able to combine a true infinite loop with user interaction as they'll both be dependent on the same thread being able to work on them exclusively. But, you can get close with a near-instant interval.
var interval = setInterval(function () {
// some code
}, 10);
Possibly grouping a few iterations together for each round:
var interval = setInterval(function () {
var limit = 5;
while (limit--) {
// some code
}
}, 10);
But, the interval will keep the iteration going as quickly as possible while still giving some idle time for user interactions, like clicking a particular button to clear the interval.
document.getElementById('stopButton').addEventListener('click', function () {
clearInterval(interval);
}, false);
Example: http://jsfiddle.net/coiscir/xZBTF/
setInterval() may be more useful here.
function updateLoop() {
//All the code goes here
}
setInterval(updateLoop,500);
var reader = new XMLHttpRequest() || new ActiveXObject('MSXML2.XMLHTTP');
function loadFile() {
reader.open('get', 'ccc.txt', true);
reader.onreadystatechange = displayContents;
reader.send(null);
}
function displayContents() {
if(reader.readyState==4) {
var el = document.getElementById('main');
el.innerHTML = reader.responseText;
var data = el.innerHTML;
}
}
for(var I = 7; I >1; i+=3);
console.log(i)
I'd like to add a class to a series of spans using setTimeout() such that the class is added in cascading fashion, creating a visual progression rather than having them all set at once. I've tried so many different ways.
Here's a codepen...
http://codepen.io/geirman/pen/nDhpd
The codepen tries to mimic a working example I've found here...
https://developers.google.com/maps/documentation/javascript/examples/marker-animations-iteration
The problem is that I can't seem to delay the addClass successively, so it happens all at once. Here's the current code.
/* The Problem Code
********************/
var span$ = $("span");
var index = 0;
var factor = 500;
function colorSpans(){
for(var i = 0; i < span$.length; i++){
setTimeout(function(){
animate();
}, factor*index);
}
index = 0;
}
function animate(){
span$[index++].className = "lg";
}
colorSpans();
One more thing, I'd love to do this sans jQuery, but will accept a jQuery solution as well.
In your current code, it looks like the very first call to animate will execute immediately, after which index will be 1. But the loop is likely to finish executing before the second timeout-handler is called (i.e., the loop won't take 500ms to execute). So this means that the remaining spans will appear instantaneously since the value of index will not be updated.
Something like this should work:
function colorSpans() {
for(var i = 0; i < span$.length; i++) {
(function(i) {
setTimeout(function() {
animate(i);
}, factor * i);
})(i); //force a new scope so that the "i" passed to animate()
//is the value of "i" at that current iteration and not
//the value of "i" when the loop is done executing.
}
}
function animate(i) {
span$[i].className = "lg";
}
The above code is also preferable because you're not using a global variable to maintain state between colorSpans and animate (i.e., we're not using index).
But you can make additional improvements by using setInterval instead:
var i = 0;
var intervalId = setInterval(function() {
if(i < span$.length) {
animate(i);
i++;
} else {
clearInterval(intervalId);
}
}, 500)
I think this is cleaner than the setTimeout approach.
Check out the updated codepens:
setTimeout approach
setInterval approach
I have a long running function. Which iterates through a large array and performs a function within each loop.
longFunction : function(){
var self = this;
var data = self.data;
for(var i=0; len = data.length; i<len; i++){
self.smallFunction(i);
}
},
smallFunction : function(index){
// Do Stuff!
}
For the most part this is fine but when I am dealing with arrays above around 1500 or so we get to the point of recieving a javascript execution alert message.
So I need to break this up. My first attempt is like so:
longFunction : function(index){
var self = this;
var data = self.data;
self.smallFunction(index);
if(data.slides[index+1){
setTimeout(function(){
self.longFunction(index+1);
},0);
}
else {
//WORK FINISHED
}
},
smallFunction : function(index){
// Do Stuff!
}
So here I am removing the loop and introducing a self calling function which increases its index each iteration. To return control to the main UI thread in order to prevent the javascript execution warning method I have added a setTimeout to allow it time to update after each iteration. The problem is that with this method getting the actual work done takes quite literally 10 times longer. What appears to be happening is although the setTimeout is set to 0, it is actually waiting more like 10ms. which on large arrays builds up very quickly. Removing the setTimeout and letting longFunction call itself gives performance comparable to the original loop method.
I need another solution, one which has comparable performance to the loop but which does not cause a javascript execution warning. Unfortunately webWorkers cannot be used in this instance.
It is important to note that I do not need a fully responsive UI during this process. Just enough to update a progress bar every few seconds.
Would breaking it up into chunks of loops be an option? I.e. perform 500 iterations at a time, stop, timeout, update progress bar, perform next 500 etc.. etc..
Is there anything better?
ANSWER:
The only solution seems to be chunking the work.
By adding the following to my self calling function I am allowing the UI to update every 250 iterations:
longFunction : function(index){
var self = this;
var data = self.data;
self.smallFunction(index);
var nextindex = i+1;
if(data.slides[nextindex){
if(nextindex % 250 === 0){
setTimeout(function(){
self.longFunction(nextindex);
},0);
}
else {
self.longFunction(nextindex);
}
}
else {
//WORK FINISHED
}
},
smallFunction : function(index){
// Do Stuff!
}
All I am doing here is checking if the next index is divisble by 250, if it is then we use a timeout to allow the main UI thread to update. If not we call it again directly. Problem solved!
Actually 1500 timeouts is nothing, so you can simply do this:
var i1 = 0
for (var i = 0; i < 1500; i++) setTimeout(function() { doSomething(i1++) }, 0)
System will queue the timeout events for you and they will be called immediately one after another. And if users click anything during execution, they will not notice any lag. And no "script is running too long" thing.
From my experiments V8 can create 500,000 timeouts per second.
UPDATE
If you need i1 passed in order to your worker function, just pass an object with it, and increment the counter inside of your function.
function doSomething(obj) {
obj.count++
...put actual code here
}
var obj = {count: 0}
for (var i = 0; i < 1500; i++) setTimeout(function() { doSomething(obj) }, 0)
Under Node.js you can aslo use setImmediate(...).
Here's some batching code modified from an earlier answer I had written:
var n = 0,
max = data.length;
batch = 100;
(function nextBatch() {
for (var i = 0; i < batch && n < max; ++i, ++n) {
myFunc(n);
}
if (n < max) {
setTimeout(nextBatch, 0);
}
})();
You might want to use requestAnimationFrame to break up your execution. Here is an example from an developers.google.com article Optimize JavaScript Execution where they even do a couple iterations a time if the task were quicker than X ms.
var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);
requestAnimationFrame(processTaskList);
function processTaskList(taskStartTime) {
var taskFinishTime;
do {
// Assume the next task is pushed onto a stack.
var nextTask = taskList.pop();
// Process nextTask.
processTask(nextTask);
// Go again if there’s enough time to do the next task.
taskFinishTime = window.performance.now();
} while (taskFinishTime - taskStartTime < 3);
if (taskList.length > 0)
requestAnimationFrame(processTaskList);
}
Is there anything better?
If you’re OK with it working only in modern browsers – then you should look into “Web Workers”, that let you execute JS in the background.
https://developer.mozilla.org/en-US/docs/DOM/Using_web_workers