I have a strange issue on the project I'm working with. This changes an image source and a content of a div automatically.
I have coded a function, but it falls into infinite loop and page does not load (page is showing the loading page always).
These are the codes:
$.fn.extend({
changehaber: function(a){
$('#cokokunanlarcontent').fadeOut('slow',function() {
$('.jquerycokokunanlarbutton').attr('src','images/sabah/buton-pasif.png');
$('img[rel="'+a+'"]').attr('src','images/sabah/buton-aktif.png');
}).html($('#'+a).html()).fadeIn('slow');
return this;
}
});
function slidecokokunanlar() {
$('#cokokunanlarcontent').html($('#cokokunanlar1').html()).delay(3000).changehaber('cokokunanlar2').delay(3000).changehaber('cokokunanlar3').delay(3000).changehaber('cokokunanlar4').delay(3000).changehaber('cokokunanlar5').delay(3000);
slidecokokunanlar();
}
slidecokokunanlar();
What's the issue here, when this is executed, I want the function to work infinitely, but the page shows it's always loading. This is the console's output:
Uncaught RangeError: Maximum call stack size exceeded
Thanks in advance
You can't call a function from inside itself without blocking up the whole execution stack. By calling the function from inside itself, you're effectively preventing it from ever returning, and as Javascript is single-threaded, everything will grind to a halt!
Change your function to this:
function slidecokokunanlar() {
$('#cokokunanlarcontent').html($('#cokokunanlar1').html()).delay(3000)...
setTimeout(slidecokokunanlar, 0);
}
This allows for concurrent execution without blocking the UI, thus allowing your page to remain responsive.
See this article on "chunking" for more information on how this works.
This is because JavaScript doesn't have proper tail calls.
Your function calls itself at the end of itself forever. The first one never finishes and returns, nor does the second, nor do any of them until you run out of stack and explode.
You might try using setTimeout instead. See an example on jsFiddle.
EDIT You might not want to use 0 unless you really need it to be running continuously. Even using 100, you'd execute the function 10 times per second.
function foo(){
console.log('foo');
setTimeout(foo, 0);
}
foo();
Here's a cleaner way to do it.
var coko = $('#cokokunanlarcontent'); // cokokunanlarcontent
var cokos = $('[id^="cokokunanlar"]').not(coko); // cokokunanlar1..2..3 etc
var total = cokos.length; // total quantity
var i = 0;
var allow = true;
$('.jquerycokokunanlarbutton').attr('src','images/sabah/buton-pasif.png');
function slidecokokunanlar( isRestart ) {
if( !isRestart ) {
$('img[rel="' + cokos[i].id + '"]').attr('src','images/sabah/buton-aktif.png');
coko.html( cokos.eq(i).html() )
.fadeIn( 'slow' );
}
if( allow ) {
coko.delay( 3000 )
.fadeOut('slow', function() {
i = (++i % total);
slidecokokunanlar(); // recursively call with next index or 0
});
}
}
slidecokokunanlar(); // start it off
function restartSlider() {
allow = true;
slidecokokunanlar( true );
}
function stopSlider() {
allow = false;
}
stopSlider(); // to stop it
restartSlider(); // to restart it
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) { ... }
I am trying to check for the presence of a modal. If the modal is not present then it will place the value of the timer into browser.sleep(). This will give time for the modal to appear. I am having an issue with a for loop in a page object. When I run the code below I do not receive the alert and console.log messages under the if when I force a failure by getting changing the object. Also, I do not receive the Timer expired message.
from page_object file (relevant code)
editVinModal: { get: function () {
return browser.element({id: 'editableVINPart'});
}},
doEditVIN: { value: function () {
modalFailedToAppear = true;
console.log('In doEditVIN');
for(modal_timer = 0 ; modal_timer <= 30; modal_timer++) {
if (!(this.editVinModal)) {
alert('In If');
console.log('Modal failed to appear');
console.log('Under if - modalFailedToAppear: ', modalFailedToAppear);
browser.sleep(modal_timer);
console.log('under if - modal_timer: ',modal_timer);
}
else {
console.log('In else if else loop');
// console.log(browser.isElementPresent(this.editVinModal));
console.log('modalFailedToAppear: ',modalFailedToAppear);
modalFailedToAppear = false;
console.log('modalFailedToAppear: ',modalFailedToAppear);
console.log('modal_timer: ',modal_timer);
break;
}
}
if (modalFailedToAppear){
console.log("Modal is not present within the given time period. Timer has expired.");
}
this.editVinLink.click();
}},
Thanks in advance for
Looks like you're new around here. Welcome!
browser.sleep(), generally speaking, does not belong in your Protractor tests (except for debugging purposes). That's the bad news. The good news is that Protractor actually provides a function that does exactly (I think) what you're trying to do. It's called browser.wait() and it works like this:
browser.wait( function() {
return element(by.id('editableVINpart')).isPresent().then( function(present) {
return present;
});
}, 5000)
.then(function() {
element(by.id('editableVINpart')).click();
}, function() {
console.log('Element not found. :( ');
});
browser.wait() takes two arguments: first, an anonymous function, which it will execute repeatedly until it returns true; second, an amount of time to wait in milliseconds (by the way, browser.sleep() also takes a millisecond wait time, so your for loop is only waiting 465 milliseconds if it iterates all the way through, or about a half second--not very long).
Then, since browser.wait() returns a promise, just like all Protractor functions, we can attach a .then() statement to the end of it, which will execute the first passed-in function if the promise is successful, or the second passed-in function if it is not.
If you often have to wait for an element to be present (and for some reason it isn't synchronized with the Angular page load), it may be useful to you to have a reusable form of the function, like this:
var waitThenClick = function(el) {
browser.wait( function() {
return el.isPresent().then( function(present) {
return present;
});
}, 5000)
.then(function() {
el.click();
}, function() {
console.log('Element with locator: ' + el.locator + ' was not found. :( ');
});
};
Then you could just call it like this, for whatever element you need:
waitThenClick(element(by.id('editableVINpart')));
Good luck! Make sure to get good and clever with asynchronous stuff (especially promises) with problems like this. Protractor promises trip up the best of us.
Sorry if this question has already been asked here before, I could not find a suitable answer.
I am wanting to create a JavaScript sleep/delay/wait function that I can call anywhere in the script, like jQuery's .delay()
I am not able to use setTimeout, as I have a script that is generated by php, and so am not able to put it into two different functions, with the timeout in the middle. I need to create a function that allows me to do
alert("time started");
sleep(4000);
alert("time up");
I really do not want to use jQuery.
Here's a solution using the new async/await syntax.
async function testWait() {
alert('going to wait for 5 second');
await wait(5000);
alert('finally wait is over');
}
function wait(time) {
return new Promise(resolve => {
setTimeout(resolve, time);
});
}
Note: You can call function wait only in async functions
You cannot just put in a function to pause Javascript unfortunately.
You have to use setTimeout()
Example:
function startTimer () {
timer.start();
setTimeout(stopTimer,5000);
}
function stopTimer () {
timer.stop();
}
EDIT:
For your user generated countdown, it is just as simple.
HTML:
<input type="number" id="delay" min="1" max="5">
JS:
var delayInSeconds = parseInt(delay.value);
var delayInMilliseconds = delayInSeconds*1000;
function startTimer () {
timer.start();
setTimeout(stopTimer,delayInMilliseconds);
}
function stopTimer () {
timer.stop;
}
Now you simply need to add a trigger for startTimer(), such as onchange.
You will have to use a setTimeout so I see your issue as
I have a script that is generated by PHP, and so am not able to put it into two different functions
What prevents you from generating two functions in your script?
function fizz() {
var a;
a = 'buzz';
// sleep x desired
a = 'complete';
}
Could be rewritten as
function foo() {
var a; // variable raised so shared across functions below
function bar() { // consider this to be start of fizz
a = 'buzz';
setTimeout(baz, x); // start wait
} // code split here for timeout break
function baz() { // after wait
a = 'complete';
} // end of fizz
bar(); // start it
}
You'll notice that a inside baz starts as buzz when it is invoked and at the end of invocation, a inside foo will be "complete".
Basically, wrap everything in a function, move all variables up into that wrapping function such that the contained functions inherit them. Then, every time you encounter wait NUMBER seconds you echo a setTimeout, end the function and start a new function to pick up where you left off.
The behavior exact to the one specified by you is impossible in JS as implemented in current browsers. Sorry.
Well, you could in theory make a function with a loop where loop's end condition would be based on time, but this would hog your CPU, make browser unresponsive and would be extremely poor design. I refuse to even write an example for this ;)
Update: My answer got -1'd (unfairly), but I guess I could mention that in ES6 (which is not implemented in browsers yet, nor is it enabled in Node.js by default), it will be possible to write a asynchronous code in a synchronous fashion. You would need promises and generators for that.
You can use it today, for instance in Node.js with harmony flags, using Q.spawn(), see this blog post for example (last example there).
You can use this -
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}
You could use the following code, it does a recursive call into the function in order to properly wait for the desired time.
function exportar(page,miliseconds,totalpages)
{
if (page <= totalpages)
{
nextpage = page + 1;
console.log('fnExcelReport('+ page +'); nextpage = '+ nextpage + '; miliseconds = '+ miliseconds + '; totalpages = '+ totalpages );
fnExcelReport(page);
setTimeout(function(){
exportar(nextpage,miliseconds,totalpages);
},miliseconds);
};
}
I have 2 setInterval function (okay guys, sorry, i thought the codes inside may be redundant and will make the question become localized :/ but anyway, here it is:
$('#armStatus').click(function(){
armingloop = setInterval(function () {
if ($checkbox.is(':checked ')) {
$.post('/request', {
key_pressed: "arming_status"
}).done(function (reply) {
$arm.empty().append("<h3>The Arming Status is " + reply + "</h3>").show();
$arm.show();
});
} else {
$arm.hide();
}
}, 3000);
});
and
$('#monitor').click(function () {
bigloop = setInterval(function () {
var checked = $('#status_table tr [id^="monitor_"]:checked');
if (checked.index() === -1 || checked.length === 0) {
clearloop(bigloop);
$('#monitor').button('enable');
} else {
//$('#monitor').button('enable'); //enable the monitor button
(function loop(i) {
//monitor element at index i
monitoring($(checked[i]).parents('tr'));
//delay of 3 seconds
setTimeout(function () {
//when incremented i is less than the number of rows, call loop for next index
if (++i < checked.length) loop(i);
}, 3000);
}(0)); //start with 0
}
}, index * 3000); //loop period
});
function clearloop(loopname){
bigloop= window.clearInterval(loopname);
}
Both will be triggered by different selector. I observe that when the bigloop is activated, and armingloop is also activated at a later time, the status update function monitoring in my bigloop is affected (e.g. status reply is captured by wrong element.)
Note that i have a setTimer as well.
My question here is, how can i make sure any 2 setIntervals are isolated and will not affect each other?
You simply can't as they have no guarantee of order. They are added to an event queue together with other events (incl. repaints etc.), and which ever comes first is called.
A better implementation would be in your main loop to throw a CustomEvent which your monitor is listening to.
Simplified:
// global flag for monitoring
var isMonitoring = true,
armingloop;
// the function we use to update monitor.
// This will be called every time we send an event
function updateMonitor(e) {
/* ... update ... */
// ie. e.detail.mydata
}
// Start listening to 'monitor' event. If received, call
// the function above (only reference the function).
window.addEventListener('monitor', updateMonitor, false);
// The main loop. Self-triggering for loop by calling
// setTimeout.
// Do the stuff you need and then, if monitor is
// enabled create an event and dispatch (send) it.
function loop() {
/* ... main stuff ... */
// do we monitor?
if (isMonitoring) {
// something need to be updated on monitor so
// create an event
var myEvent = new CustomEvent('monitor', {
detail: {
/* here you can provide needed data for monitor */
"mydata": data /*, other data */
},
/* If you don't need to send any data in particular,
just leave detail empty like this:
detail: {},
*/
bubbles: false,
cancelable: true
});
// send event to anyone who listens..
window.dispatchEvent(myEvent);
}
//here you can use a use a flag to stop the loop,
//f.ex. if 'isLooping' === true then setTimeout...
armingloop = setTimeout(loop, 3000);
}
function toggleMonitor() {
// Call this from the toggle button, or modify to
// reflect checkbox-status etc...
isMonitoring = !isMonitoring;
}
//start everything:
loop();
I changed the example a bit from setInterval to setTimeout to avoid stacking/blocking. Also keep in mind that Javascript is single-threaded (with a few exceptions that are not relevant here). For this reason setTimeout is a better choice (call it from inside the loop).
How can I make sure any 2 setIntervals are isolated and will not affect each other?
Variable scope
Make sure that all of the variables involved are correctly scoped, and avoid adding any to the global scope unless it's completely unavoidable (this shouldn't be the case). This means you'll want to be using the var keyword whenever you declare any variables.
If your variables are correctly scoped to their respective setInterval calls then there's no danger of one affecting values in the other, even if you've used the same variable names.
Check your logic
If you're querying, and then modifying, the same set of elements on the page in both of them then they can't be independent, since changes in one of them will then be reflected in the next execution of the other one. Any shared logic, any use of global variables, etc. are all potential candidates for issues to be introduced.
Essentially you're looking for any overlap between the two, and then (hopefully) eliminating that. If it can't be eliminated then your two setIntervals can't be isolated, and you either have to accept that the two are linked or find another approach to solving the problem.
How are the intervals triggered?
Maybe you can try to call them in for example a click function:
$('<ELEMENT>').click( function() {
setInterval(function(){
},3000);
});
$('<ELEMENT>').click( function() {
setInterval(function () {
var checked = $('#status_table tr [id^="monitor_"]:checked');
if (checked.index()===-1 ||checked.length===0){
clearloop(bigloop);
$('#monitor').button('enable');
}else{
//$('#monitor').button('enable'); //enable the monitor button
(function loop(i) {
//monitor element at index i
monitoring($(checked[i]).parents('tr'));
//delay of 3 seconds
setTimeout(function () {
//when incremented i is less than the number of rows, call loop for next index
if (++i < checked.length) loop(i);
}, 3000);
}(0)); //start with 0
}
}, index*3000); //loop period
});
I have a javascript function that is being built to animate the collapse of a div, and then proceed with other jobs. The code is as follows:
function newsFeed() {
var self = this;
this.collapse = function(listingID,orig_height,curr_height,opacity) {
var listing = document.getElementById(listingID);
var reduceBy = 5;
if(curr_height > reduceBy) {
curr_height = curr_height-reduceBy;
listing.style.overflow = "hidden";
listing.style.height = (curr_height-40) + "px";
if(opacity > 0) {
opacity = opacity - 10;
var opaque = (opacity / 100);
listing.style.opacity=opaque;
listing.style.MozOpacity=opaque;
listing.style.filter='alpha(opacity='+opacity+')';
}
setTimeout(function() { self.collapse(listingID,orig_height,curr_height,opacity); },1);
}else{
return true;
}
}
this.remove = function(listingID) {
var listing = document.getElementById(listingID);
var currHeight = listing.offsetHeight;
if (this.collapse(listingID,currHeight,currHeight,100)) {
// DO SOME OTHER STUFF
}
}
}
var newsFeed = new newsFeed();
newsFeed.remove('closeMe');
I cannot get the this.remove function to wait while this.collapse finishes and returns true. Is this impossible? What is the best way to go on?
Important: I would like to be able to use this.collapse with other functions yet to be built in the same fashion as I do here.
I cannot get the this.remove function to wait while this.collapse finishes
That is correct, it is impossible to do so. In JavaScript there is a single flow of execution. When the browser calls your code you can do some processing, but for anything further to occur (timeouts or event calls) you must return control to the browser.
‘Asynchronous’ processes like collapse() are done by setting timeouts, so control must be returned to the browser many times; when remove() calls collapse() the first time it returns immediately after the first timeout is set; that timeout cannot be fired until remove() itself returns, so your 'if' code will only ever execute if the very first call to collapse() was the last frame of animation (ie. the element was 5px or smaller already). Otherwise collapse()'s ‘return true’ will just be returning true to the browser's timeout-caller, which doesn't care at all what value you return to it.
Some languages give you tools such as threads or coroutines that can allow an asynchronous routine to be run from a synchronous routine; JavaScript does not. Instead, remove() must supply collapse() with a callback function it can call itself on the last frame.
There is no way you can pause the execution in Javascript till something else happens. All you can do is attach a callback function to collapse to call after it is done executing the final step.
As a sidenote, jQuery provides functions like fade(), animate() etc and supports queuing. If you don't want to use jQuery, you can still look at the code to see how it's implemented.
See the examples in this page.
setTimeout is not a "sleep". The function will end right there and return "undefined".
To manage that, I think you should do something like:
var newsFeed = new newsFeed();
newsFeed.onaftercollapse = function () {
newsFeed.remove('closeMe'); // "newsFeed" or "self"? must test
};
And then instead of return true;, the collapse() will end with:
if (self.onaftercollapse) self.onaftercollapse();
This example demonstrates how to check if a function is complete.
function foo() {
foo.complete = false;
// your code here
foo.complete = true;
}
foo.complete = false;
if (foo.complete) { // foo execution complete
// your code here
}
This code demonstrates how to check if a function has been run once.
function foo() {
// your code here
foo.ranOnce || (foo.ranOnce = true);
}
foo.ranOnce = false;
if (foo.ranOnce) { // foo execution complete at least once
// your code here
}