Function not being called synchronously? - javascript

I'm having a problem with some code not performing as expected, I should probably explain what it's doing first:
On document load function selectForLists is querying a sqlite DB
containing football scores, specifically a table called matches, then
calling function renderLists.
RenderLists puts the playing team into a sorted list with duplicates
removed.
Then for each entry in this list of teams function latestTest is
being called, which selects all rows from the match table where that
team is playing and calls latestTest2.
LatestTest2 counts the number of rows with that team playing and
outputs some code to the inserted div.
Once that has been completed for every team it should revert to
finish the renderLists function and call the loaded function, except
it doesnt and I have to add a delay to calling this function because
it doesnt happen last.
I'm hoping someone can tell me whats wrong here, why the loaded function isn't called after all the above is completed? Also if anyone has any tips for achieving the same outcome with more efficient code I would like that very much.
Apologies for this long post, I'm sure many will find the code terrible and I know there are too many functions and probably many better ways of doing this but its been a few years since working with javascript in uni and I'm struggling with it and sqlite.
The code is below or at http://pastebin.com/7AxXzHNB thanks
function selectForLists() { //called on (document).ready
db.transaction(function(tx) {
tx.executeSql('SELECT * FROM matches', [], renderLists);
});
}
function renderLists(tx, rs) {
var playingList = new Array();
for (var i = 0; i < rs.rows.length; i++) {
playingList.push(rs.rows.item(i)['playing']);
}
playingListSort = playingList.sort();
var playingListFinal = new Array();
playingListSort.forEach(function(value) {
if (playingListFinal.indexOf(value) == -1) {
playingListFinal.push(value);
}
});
for (var c = 0; c < playingListFinal.length; c++) {
latestTest(playingListFinal[c]);
}
loaded(); //not running last in the function
//setTimeout(loaded,1000);
/////Using a delay because it doesn't run after the above has completed
}
function latestTest(team) {
db.transaction(function(tx) {
tx.executeSql('SELECT * FROM matches WHERE playing="' + team + '"', [], latestTest2);
});
}
function latestTest2(tx, rs) {
counted = rs.rows.length;
var theFunction = rs.rows.item(0)['playing'];
$('#inserted').append('<li onclick="onToDate(\'' + theFunction + '\')"><img width="30px" height="25px" id="popupContactClose" src="style/soccer.png"><div id="popupContactClose2">' + counted + '</div></img>' + rs.rows.item(0)['playing'] + '</li>');
}

Both db.transaction and tx.executeSql are asynchronous functions, just like setTimeout, where if you write
setTimeout(function(){
doLater();
}, 1000)
doNow();
doNow() will execute before doLater() because the callback function you created will get called at some future time.
In your case, latestTest() calls db.transaction, and then tx.executeSql, both of which are asynchronous. That means, the callback function latestTest2 will get called at some future time, which will be after when loaded() gets called.

The latestTest function calls another executeSQL function with its own callback. That callback will be executed when the SQL has finished, which will be at an arbitrary time.
The renderLists function will continue execution (including calling the loaded function) normally, quite apart from anything having to do with the callbacks in latestTests being executed.
Your mistake is thinking that loaded will "wait" to be executed--you will still have pending callbacks from the DB code in latestTest.

Related

JavaScript: Stop a For loop with setTimeout function and this keyword

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) { ... }

Javascript function wait fo other one to occur

hello Im new to javascript and I cant figure how to make function wait for other one to finish, here is my code:
// other code
db.transaction(function (transact,callback) {
transact.executeSql('SELECT * FROM buildings WHERE id = ?', [id], function (transact, results) {
if (results.rows.length > 0) {
buildingsCache.push(JSON.parse(results.rows.item(0).data));
index = buildingsCache.length - 1;
}
else {
alert("Building not found in database!");
}
});
});
return buildingsCache[index];
// other code
The problem is that current to function returns value before its set in subfunction. I would need to do something like this:
// other code
var FINISHED=false;
db.transaction(function (transact,callback) {
transact.executeSql('SELECT * FROM buildings WHERE id = ?', [id], function (transact, results) {
if (results.rows.length > 0) {
buildingsCache.push(JSON.parse(results.rows.item(0).data));
index = buildingsCache.length - 1;
}
else {
alert("Building not found in database!");
}
});
FINISHED=true;
});
while(!FINISHED);
return buildingsCache[index];
// other code
but its not working. I checked some other solutions here, but didnt make work any of it. Can you help pls?
You need to change your code so the transaction callback is the one that calls the functions that are waiting for it. Basically, your function to fetch the buildingsCache will not return a value. Instead, it will receive a callback and pass it the buildingsCache when its available. This is the same pattern that the db.transaction function uses
function fetchBuildingsCache(onDone){
db.transaction(function (transact,callback) {
transact.executeSql('SELECT FROM...', [id], function (transact, results) {
//process db results...
//then "return" by calling our callback
onDone(buildingsCache[index]);
});
});
}
//and this is how you use it:
fetchBuildingsCache(function(cache){
console.log(cache);
}
Of course, one side effect of this pattern is that every function that calls fetchBuildingsCache and all functions that call those functions and so on will also have to be converted to callback-passing style.
Sadly, this is unavoidable in Javascript. The best you can do is use a higher level library (like promises or async.js) or use one of those Javascript transpilers that add async/await syntax to Javascript.
Use promises for this purpose.
Take a look at Q js library: http://github.com/kriskowal/q
You've a callback in db.transaction(function (transact,callback), use it properly to execute next set of code once executeSql is done.
A promise represents the eventual result of an asynchronous operation. It is a placeholder into which the result will be materialized.
Use it properly callback vs. promise.

Synchronous WebSQL Transaction with another transaction inside

I know that I cannot retrieve the value of transaction Synchronously. I Read that an alternative is to Callback the function. But I can't manage to do it.. here's a snippet where I inserted the second transaction inside a function.
for(...){
db.transaction(function(tx) {
tx.executeSql('SELECT ...', function(tx, results) {
...
...
weight = weightRetrieve(month, year);
calories = 0.9 * km * weighta;
});
update+=xxx+calories+yyy;
} //for end
$("#form").html(update).trigger('create');
And this is the second function:
function weightRetrieve(month, year) {
db.transaction(function(tx) {
tx.executeSql('SELECT weight,day, ......etc) {
weight=results.rows.item(i).weight;
return weight;
}
}
I cutted out a lot of code, but the only problem I'm facing is that I cannot retrieve the weight from the second function neither this way or inside the first transaction cause if works async
It would be bad programming if I would store values with localStorage?
As a general rule, if you are using asynchronous calls, trying to "return" a value isn't going to do you much good. The outer function has usually finished executing and returned the variable well before the inner asynchronous function has put any data in the variable, so you end up returning an empty variable. Additionally in your case, your "return" is returning from the anonymous function you passed to db.transaction rather than weightRetreive.
You instead need to pass in a callback function as a parameter, then call that callback function with the value once you have it.
For example:
function weightRetrieve(month, year, onSuccess) {
db.transaction(function(tx) {
tx.executeSql('SELECT weight,day, ......etc', [], function(tx, results) {
weight=results.rows.item(i).weight;
onSuccess(weight);
});
});
}
Notice the addition of the parameter "onSuccess" as a callback function.
Because this is all in a for loop, you'll need to give each iteration the chance to finish and add its "calories" calculation to your final accumulated value before continuing. To do this you can create a function to call at the end of each iteration, which keeps track of the number of times it has been called and compares it to the length of the thing you're looping over before executing the final code:
var updateFinalValue = (function() {
var numCalls = 0; // number of times the function has been called
var totalIterations = thingYouLoopOver.length; // number of times the for loop will run
var finalValue = 0; // total of all your "calories" calculations
return function(additionalValue) {
finalValue += additionalValue;
if(++numCalls == totalIterations ) {
document.getElementById("totalCalories").value = finalValue;
doSomethingWithFinalValue(finalValue);
// etc
}
};
})();
The syntax may look a bit weird if you're not overly familiar with javascript, but it's the inner function being returned that gets executed when you call "updateFinalValue". The outer function is evaluated immediately (notice the "();" right at the end - we're calling the outer function in-place) and serves purely to provide a scope for the inner function. This is known as a "closure", and among other things is a good way to encapsulate the variables rather than having to make them global.
You would then use this as follows:
for(...) {
db.transaction(function(tx) {
tx.executeSql('SELECT ...', [], function(tx, results) {
...
...
weightRetrieve(month, year, function(weight) {
var calories = 0.9 * km * weight;
updateFinalValue(calories);
});
});
});
}

How to make a javascript FOR LOOP wait for certain conditions before looping

I have to call up a function (checkImdb) that will fetch some info from a php file (temp.php) and put some contents on a div (placeToFetchTo). This has to be done a certain amount of times, so I used a FOR LOOP for that.
The problem is that only the last instance of the looped counter (currentCastId) gets used. I understand there needs to be a way to force the FOR LOOP to wait for the fetch to be complete, and I have been looking online for answers but nothing seems to work so far. I apologise if I have missed an eventual answer that already exists.
Any help is appreciated.
This is the code I am referring to:
function checkImdb (totalCasts) {
$(function() {
for (currentCastId = 1; currentCastId <= totalCasts; currentCastId++) {
//Gets cast IMDB#
var row = document.getElementById("area2-" + currentCastId)
row = row.innerHTML.toString();
var fetchThis = "temp.php?id=" + row + "\ .filmo-category-section:first b a";
placeToFetchTo = "#area0-" + currentCastId;
function load_complete() {
var filhos = $(placeToFetchTo).children().length, newDiv ="";
var nrMoviesMissing = 0, looped = 0;
alert("done- "+ placeToFetchTo);
}
document.getElementById("area0").innerHTML = document.getElementById("area0").innerHTML + "<div id=\"area0-" + currentCastId + "\"></div>";
$(placeToFetchTo).load(fetchThis, null, load_complete);
} //End of: for (imdbLooper = 0; imdbLooper <= totalCasts; imdbLooper++) {
}); //End of: $(function() {
}
2017 update: The original answer had the callback arg as last arg in the function signature. However, now that the ES6 spread operator is a real thing, best practice is to put it first, not last, so that the spread operator can be used to capture "everything else".
You don't really want to use a for loop if you need to do any "waiting". Instead, use self-terminating recursion:
/**
* This is your async function that "does things" like
* calling a php file on the server through GET/POST and
* then deals with the data it gets back. After it's done,
* it calls the function that was passed as "callback" argument.
*/
function doAsynchronousStuff(callback, ...) {
//... your code goes here ...
// as final step, on the "next clock tick",
// call the "callback" function. This makes
// it a "new" call, giving the JS engine some
// time to slip in other important operations
// in its thread. This basically "unblocks"
// JS execution.
requestAnimationFrame(function() {
callback(/* with whatever args it needs */);
});
}
/**
* This is your "control" function, responsible
* for calling your actual worker function as
* many times as necessary. We give it a number that
* tells it how many times it should run, and a function
* handle that tells it what to call when it has done
* all its iterations.
*/
function runSeveralTimes(fnToCallWhenDone, howManyTimes) {
// if there are 0 times left to run, we don't run
// the operation code, but instead call the "We are done"
// function that was passed as second argument.
if (howManyTimes === 0) {
return fnToCallWhenDone();
}
// If we haven't returned, then howManyTimes is not
// zero. Run the real operational code once, and tell
// to run this control function when its code is done:
doAsynchronousStuff(function doThisWhenDone() {
// the "when done with the real code" function simply
// calls this control function with the "how many times?"
// value decremented by one. If we had to run 5 times,
// the next call will tell it to run 4 times, etc.
runSeveralTimes(fnToCallWhenDone, howManyTimes - 1);
}, ...);
}
In this code the doAsynchronousStuff function is your actual code.
The use of requestAnimationFrame is to ensure the call doesn't flood the callstack. Since the work is technically independent, we can schedule it to be called "on the next tick" instead.
The call chain is a bit like this:
// let's say we need to run 5 times
runSeveralTimes(5);
=> doAsynchronousStuff()
=> runSeveralTimes(5-1 = 4)
=> this is on a new tick, on a new stack, so
this actually happens as if a "new" call:
runSeveralTimes(4)
=> doAsynchronousStuff()
=> runSeveralTimes(4-1 = 3), on new stack
runSeveralTimes(3)
...
=> doAsynchronousStuff()
=> runSeveralTimes(1-1 = 0), on new stack
runSeveralTimes(0)
=> fnToCallWhenDone()
=> return
<end of call chain>
You need to use a while loop and have the loop exit only when all your fetches have completed.
function checkImdb (totalCasts) {
currentCastId = 1;
totalCasts = 3;
doneLoading = false;
while (!doneLoading)
{
//do something
currentCastId++;
if (currentCastId == totalCasts)
doneLoading = true;
}
}

Understanding closures: Constructing a meta-function that queues functions together

In terms of solving the problem, I have a fully working solution that I just finished here:
// synchronous dynamic script loading.
// takes an array of js url's to be loaded in that specific order.
// assembles an array of functions that are referenced more directly rather than
// using only nested closures. I couldn't get it going with the closures and gave up on it.
function js_load(resources, cb_done) {
var cb_list = []; // this is not space optimal but nobody gives a damn
array_each(resources, function(r, i) {
cb_list[i] = function() {
var x = document.body.appendChild(document.createElement('script'));
x.src = r;
console.log("loading "+r);
x.onload = function() {
console.log("js_load: loaded "+r);
if (i === resources.length-1) {
cb_done();
} else {
cb_list[i+1]();
}
};
};
});
cb_list[0]();
}
I am completely happy with this because it does what I want now, and is probably far easier to debug than what my first approach, if it had succeeded, would have been.
But what i can't get over is why I could never get it to work.
It looked something like this.
function js_load(resources, cb_done) {
var cur_cont = cb_done;
// So this is an iterative approach that makes a nested "function stack" where
// the inner functions are hidden inside the closures.
array_each_reverse(resources, function(r) {
// the stack of callbacks must be assembled in reverse order
var tmp_f = function() {
var x = document.body.appendChild(document.createElement('script'));
x.src = r;
console.log("loading "+r);
x.onload = function() { console.log("js_load: loaded "+r); cur_cont(); }; // TODO: get rid of this function creation once we know it works right
};
cur_cont = tmp_f; // Trying here to not make the function recursive. We're generating a closure with it inside. Doesn't seem to have worked :(
});
cur_cont();
}
It kept trying to call itself in an infinite loop, among other strange things, and it's really hard to identify which function a function is and what a function contains within it, during debugging.
I did not dig into the code, but it appears that jQuery.queue has also implemented a similar mechanism to my working one (using an array to track the queue of continuations) rather than using only closures.
My question is this: Is it possible to build a Javascript function that can take a function as argument, and enhance it with a list of other functions, by building closures that wrap functions it creates itself?
This is really hard to describe. But I'm sure somebody has a proper theory-backed mathematical term for it.
P.S. Referenced by the code above are these routines
// iterates through array (which as you know is a hash), via a for loop over integers
// f receives args (value, index)
function array_each(arr, f) {
var l = arr.length; // will die if you modify the array in the loop function. BEWARE
for (var i=0; i<l; ++i) {
f(arr[i], i);
}
}
function array_each_reverse(arr, f) {
var l = arr.length; // will die if you modify the array in the loop function. BEWARE
for (var i=l-1; i>=0; --i) {
f(arr[i], i);
}
}
The problem is how you were setting the value of cur_cont for every new function you made, and calling cur_cont in the onload callback. When you make a closure like tmp_f, any free variables like cur_cont are not 'frozen' to their current values. If cur_cont is changed at all, any reference to it from within tmp_f will refer to the new, updated value. As you are constantly changing cur_cont to be the new tmp_f function you have just made, the reference to the other functions are lost. Then, when cur_cont is executed and finishes, cur_cont is called again. This is exactly the same function that had just finished executing - hence the infinite loop!
In this sort of situation, where you need to keep the value of a free variable inside a closure, the easiest thing to do is to make a new function and call that with the value you want to keep. By calling this new function, a new variable is created just for that run, which will keep the value you need.
function js_load(resources, cb_done) {
var cur_cont = cb_done;
array_each_reverse(resources, function(r) {
// the stack of callbacks must be assembled in reverse order
// Make a new function, and pass the current value of the `cur_cont`
// variable to it, so we have the correct value in later executions.
// Within this function, use `done` instead of `cur_cont`;
cur_cont = (function(done) {
// Make a new function that calls `done` when it is finished, and return it.
// This function will become the new `cur_cont`.
return function() {
var x = document.body.appendChild(document.createElement('script'));
x.src = r;
console.log("loading "+r);
x.onload = function() {
console.log("js_load: loaded "+r);
done();
};
};
})(cur_cont);
});
// Start executing the function chain
cur_cont();
}
EDIT: Actually, this can be made even simpler by using the Array.reduce function. Conceptually, you are taking an array and producing a single function from that array, and each successive function generated should be dependant upon the last function generated. This is the problem that reduce was designed to help solve:
function js_load(resources, done) {
var queue = resources.reduceRight(function(done, r) {
return function() {
var x = document.body.appendChild(document.createElement('script'));
x.src = r;
console.log("loading "+r);
x.onload = function() {
console.log("js_load: loaded "+r);
done();
};
};
}, done);
queue();
};
Note that reduce and reduceRight are not available for older browsers (<= IE8). A JavaScript implementation can be found on the MDN page.

Categories

Resources