I am have the following code. it causes a stack overflow exception.
any idea what did I do wrong?
var myApi = {
rawData: null,
initData: function() {
// ajax call to get data and populate myApi.rawData, max 10 seconds
},
waitForRawData: function(callback) {
if(myApi.rawData === null || myApi.rawData.length ===0) {
window.setTimeout(myApi.waitForRawData(callback),1000); // complain this line stack overflow
}else{
callback();
}
},
updateHtmlWithNewData: function() {
// base on myApi.rawData update html element
},
workflow: function() { // this function call is invoke from page
myApi.initData();
myApi.waitForRawData(myApi.updateHtmlWithNewData);
}
}
You have an infinite loop.
setTimeout expects the first parameter to be a callback function - you're actually invoking the waitForRawData function then and there. Which immediately invokes itself again, which immediately invokes itself again, which... you get the idea.
Do this:
window.setTimeout(function() { myApi.waitForRawData(callback) },1000);
When you pass it as a function, then the timeout can invoke it whenever you tell it to - in your case, a second later. Doing it without the wrapping function is calling that same code right now.
Related
Is it possible to call a function in JavaScript that calls itself again, and after it calls itself again, finally runs the initial callback function?
I will explain what I mean in code...
//on page load
getProviderNextAppointment(null, function(nextAppointment) {
otherFunction(); //<----- how can I always end up back here, no matter what?
});
//getProviderNextAppointment function
function getProviderNextAppointment(startDate, callback) {
getNextAppointment('provider', startDate, function(data) {
//if provider has schedule
if(!$.isEmptyObject(data.AllProviders)) {
//set provider params
//nextAppointment = data.x
//callback
if(typeof callback === 'function') {
return callback(nextAppointment); //callback from call on page load
}
} else {
if(data.ErrorCode !== 'StartDateTooFarInFuture') {
/*---------->
* this is where we this function calls itself again;
* but when it hits the callback above (provider
* has schedule), or when it hits the callback
* below (last group of appointments), it
* should run the initial callback to
* execute otherFunction()
<----------*/
getProvidersNextAppointment(data.LatestDate);
} else { //hit last group of appointments
if(typeof callback === 'function') {
return callback(null); //callback from call on page load
}
}
}
});
}
I did not include getNextAppointment() function, because it is irrelevant to the question. Just know that it is calling an API that is returning the appointment information, as well as a LatestDate property that we are using as the startDate for the next API call. We are also looking for a response for the ErrorCode property that says it's the end of the results, so that we are not looping forever.
If you need to keep a reference to the initial callback function, then you could just pass it on via the recursive call.
Change the recursive call from this:
getProvidersNextAppointment(data.LatestDate);
to:
getProvidersNextAppointment(data.LatestDate, callback);
Now the nested function context will have a reference to the original callback function, and this could in theory cascade further down the recursion tree until the callback function is finally executed.
I'm having a synchronization and loading issues with some JS modules when the program starts. This error only shows up once at the beginning and then everything works, so it is an obvious sync problem.
The code:
//pyramid of doom
function initGame(){
initWorld(function(){
initPlayer(function(){
initBots(function(){
console.log("Game Loaded!");
update();
})
})
});
}
function initWorld(callback){
world.init(worldParams);
callback&&callback();
}
function initPlayer(callback){
player.init(scene,playerParams,world.getPhysicModel());
callback&&callback();
}
function initBots(callback){
bots.init(scene,botsParams,world.getPhysicModel());
callback&&callback();
}
function update() {
world.update(1/60);
player.update();
bots.update();
}
initGame();
The following is the error I'm getting.
Bots.js:112 Uncaught TypeError: Cannot read property 'mixer' of undefined
at Bots.update (Bots.js:112)
at update (Final.html:160)
What am I doing wrong? How can I synchronize the execution of the init functions?
(What I think that is going on is that the execution of initBots doesn't reach it end before the udpdate function starts to run.)
You can find the Bots.js module in my repository at ( 1 )
In bots.init you execute new THREE.ColladaLoader().load which looks to be asynchronous.
In its callback, you fill your _bots array (self._bots[modelLoaded] = bot;).
However, you execute bots.init() and do not wait for these asynchronous calls to complete before executing the initBots function callback. In the case of initGame execution, this callback executes update(), which in turn executes bots.update(), which tries to access this._bots[i].mixer with i index up to this._botsParams.length, i.e. a pre-defined value that does not account for how many items have been actually filled in _bots array.
Hence your error message: the array has no items yet at some indices, and trying to read a property on undefined throws an error.
Conclusion: common asynchronous issue.
You need to be passing the callbacks into the init functions. Guessing from a brief look at your bots code, they are not expecting to receive callbacks so you might be in for a rebuild.
You can't tell from the outside of an async function if it is done yet!
Equivalent to what you are doing:
let result = undefined
function takeTime () {
setTimeout(function() {
result = 'hello!'
}, 100)
}
function callback () {
console.log(result)
}
function run (callback) {
takeTime()
callback && callback()
}
run(callback) // undefined! takeTime has not finished yet
What you need to be doing:
let result = undefined
function takeTime (callback) {
setTimeout(function() {
result = 'hello!'
callback()
}, 100)
}
function callback () {
console.log(result)
}
function run (callback) {
callback && takeTime(callback)
}
run(callback) // 'hello!', the callback was only called once the takeTime function completed
Does the following code logic cause the original call's stack frame to contain the memory from each subsequent call (causing excessive memory usage)?
function foo (arg) {
bar(arg);
}
function bar (arg) {
$.ajax({
success: function (data) {
if (data['result'] == 'continue') {
bar(data['nextarg']);
} else if (data['result'] == 'done') {
alert('done!');
}
}
});
}
Your code is not recursive. $.ajax is asynchronous, so the stack pointer isn't waiting for bar to return.
Instead, $.ajax fires an asynchronous process, then continues until it hits either an explicit or implicit return. In your case, there is an implicit return at the end of bar.
Your function consumes no more memory than it should.
function bar (arg) {
// calls $.ajax, which is async, so it fires "whenever"
$.ajax({
// when the ajax is complete/successful, this function is called
success: function (data) {
if (data['result'] == 'continue') {
bar(data['nextarg']);
} else if (data['result'] == 'done') {
alert('done!');
}
}
})
// exits immediately after
}
I was curious to see if this was the case, so I tested it using a simplified version. The code below is an ajax call that binds calls to itself in its own success routine, printing the call stack each time. As it turns out, the call stack is the same each time, i.e. no memory leak.
I have a hunch that the fact that the call is asynchronous may come into play - i.e. there isn't actually any recursion since the success handler is called by the browser directly on the success of the AJAX call, not from within the last invocation of the function.
Here is the code I used to test the hypothesis:
var count = 0;
function bar() {
$.ajax("/", {
success: function () {
count++;
console.trace();
if (count < 4) bar();
}
});
}
bar();
And here is a live JSFiddle that shows you that the call stack is exactly the same on each invocation: https://jsfiddle.net/dtrgak9o/
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.
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