I have a javascript array. I need to process each element of this array and then store the entire array into an object store. The kind of processing I do on the elements should be async. as shown in the following method.
var x = [ele1,ele2,ele3] ;
x.forEach(function(ele,index){
if(ele == "some specific object"){
MyAPI.process("command",function(result){
x[index] = result;
});
database.store(x);
The problem when calculating asynchronously is that the elements aren't processed in order. The key would be to keep track of the number of results that have been processed then, and only then, call the store method. The most effective way is to move the call to the store method into the callback. That way you can call it directly after the check for the last element succeeds rather than having to do a busy/wait while checking the flag condition. Another possibility would be to raise an event when the last element is processed and have the store take place in the event handler (or something similar).
var x = [ele1,ele2,ele3] ;
var expectedResults = 0;
var results = 0;
x.forEach(x,function(ele,index) {
if (ele == "some specific object") {
++expectedResults;
}
});
x.forEach(x,function(ele,index){
if (ele == "some specific object"){
MyAPI.process("command",function(result){
x[index] = result;
if (++results == expectedResults) {
database.store(x);
}
});
}
});
Related
How to callback each loop after completion of ajax call.
Please find my code as follows.
Story:
Let us assume I have 3 values X,Y,Z. Firstly, I am taking X value, sending to django views and their using requests module for getting some info and pushing to the div class push_new_info_here, in the next Iteration I have to take Y value. How to do ? please note: Previous ajax call should be succeeded .
Final word: I am collecting all the info of (X,Y,Z), then merging using python and pushing to the div class push_new_info_here
window.onload = function() {
$.each(["X","Y","Z"], function( index, value ) {
$.post('/find/'+value+'/',{},function(data){
$('.push_new_info_here').empty();
$('.push_new_info_here').html(data);
});
});
};
Like this - use .append if you want to see the data or change the timeout to something long enough for the user to see:
var arr = ["X","Y","Z"],cnt=0;
function postIt() {
if (cnt >= arr.length) return; // stop
$.post('/find/'+arr[cnt]+'/',{},function(data){
$('.push_new_info_here').empty().html(data); // or .append
cnt++;
postIt(); // or setTimeout(postIt,3000); // give server a breather
});
}
$(function() {
postIt();
});
try wrapping the call in a function, then recursing through the array using the AJAX .done method. For example
window.onload = function() {
recursiveAjax(["X","Y","Z"])
};
function recursiveAjax(values){
//basic error testing
if (typeof values == "undefined" || values.length == 0) return false
var value = values.pop();
$.post('/find/'+value+'/',{},function(data){
$('.push_new_info_here').empty();
$('.push_new_info_here').html(data);
recursiveAjax(values)
});
}'
EDIT:
To avoid destroying the array we can send a cloned copy through to the function. For example:
window.onload = function() {
var tempArray = ["X","Y","Z"]
recursiveAjax(tempArray.slice())
};
I am trying to return whether a user already exists in a MongoDB. Running console.log within collection.find() prints the correct amount (greater than 0). However, when userExists is called, it always returns false (0).
How do I make Javascript wait for these functions to complete before returning a value? I've read about jQuery's $.Deffered(), but this feels dirty to me, and it didn't work.
function userExists(db, uid){
var collection = db.get('users');
var ret = 0;
collection.find({"uid":uid},{},function(e,docs){
ret = docs.length
});
return ret > 0?true:false;
}
As some have noted, collection.find is asynchronous, so when you reach the next line in userExists (the line where you've got return ret > 0?true:false;), it's too early and the value of ret hasn't been set. Anywhere outside of the callback to collection.find (and any functions it calls in turn), the query hasn't happened yet.
There is (basically) no way to "pause" userExists until after the query, so you need to change your whole approach. What you need is the Continuation Pattern. This means that whatever you're doing with the result of collection.find has to happen in the callback.
I don't know what you're trying to do with ret, so this might mean big changes to how your code is organized. Here's an outline that I hope will give you the general idea:
function processResultAndDisplayToTheUser(ret) {
//everything that needs to happen after the value of ret is set
if (ret > 0) {
doSomething();
} else {
doSomethingElse();
}
}
function userExists(db, uid){
var collection = db.get('users');
//ret doesn't exist here
collection.find({"uid":uid}, {}, function(e,docs){
var ret = docs.length;
//ret finally exists, so pass it to a function and use it
processResultAndDisplayToTheUser(ret);
});
//ret doesn't exist here either
}
//ret still doesn't exist here
I took the hint and ended up restructuring my code. I created a function addUpdateUser(), did the count there, then ran the addUser() or updateUser() functions accordingly.
addUpdateUser(db, {
"uid" : uid,
});
function addUpdateUser(db, userInfo){
var collection = db.get('users');
collection.find({"uid":userInfo.uid},{},function(e,docs){
if(docs.length > 0){
updateUser(db, userInfo)
}else{
addUser(db, userInfo)
}
});
}
since collection.find is asynchronous method that doesn't return immediately you need to change your code to,
you can pass a callback function
function userExists(db, uid,callback){
var collection = db.get('users');
collection.find({"uid":uid},{},function(e,docs){
callback(docs.length);
});
}
now you can call this userExists function as
userExists(db, uid,function(ret){
//do something here
})
I have a search box that hides all lines in a list that don't contain the entered text.
This worked great until the list became 10,000 lines long. One keystroke is fine but if the user types a several letter word, the function is iterated for each keypress.
What I want to do is to abandon any previous execution of the function if a new key is pressed.
The function is very simple, as follows:
$("#search").keyup(function(e) {
goSearch();
});
function goSearch()
{
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(function(index, element) {
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(searchString,0) != -1)
$(row).show();
else
$(row).hide();
});
}
Thanks
You can't directly. Javascript is not multi-threaded so your function will run and block any key-presses until it is done.
The way this is made tolerable from a user-experience point of view is to not trigger a function immediately on a key event, but to wait a short period of time and then fire the event.
While the user is typing, the timeout function will continually be set and reset and so the gosearch function won't be called, and so the user won't have their typing interrupted.
When the user pauses typing, the timeout will countdown to zero and call the search function, which will run and block typing until it completes. But that's okay (so long as it completes within a second or so) as the user is probably not currently trying to type.
You can also do what you actually asked by breaking up your gosearch function into chunks, where each call to the function: * Reads a counter of the number of lines processed so far, and then processes another 500 lines and increments the counter. * Calls another gosearch using setTimeout with a value of zero for the time. This yields events to other 'threads', and allows for fast changing of search terms.
var goSearchTimeout = null;
var linesSearched = 0;
function keySearch(e){
if(goSearchTimeout != null){
clearTimeout(goSearchTimeout);
linesSearched = 0;
}
goSearchTimeout = setTimeout(goSearch, 500);
}
$("#search").keyup(keySearch);
function highLight(index, element) {
if(index >= linesSearched){
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(searchString,0) != -1){
$(row).show();
else{
$(row).hide();
}
if(index > linesSearched + 500){
linesSearched = index;
goSearchTimeout = setTimeout(goSearch);
return;
}
}
function goSearch(){
goSearchTimeout = null;
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(highLight);
}
If you're going to use timeout callbacks like this, I'd strongly recommend wrapping your code up into jQuery widgets, so that you can use variables on the object to store the variables goSearchTimeout etc rather than having them float around as global variables.
Introduce a counter var keypressCount that is being increased by your keypress event handler. at the start of goSearch() write its value into a buffer. Then at each run of your $(".lplist").each() you ask if the current keypressCount is the same as the buffered one; if not, you return. I would suggest you use a for() though since it is easier to break; than $.each().
Update:
You will have to make sure that there is time for new keypress events to be fired/received, so wrap the anonymous function of your $.each() inside a timeout.
Reference: http://www.garrickcheung.com/javascript/what-i-learned-about-multi-threading-in-javascript/
You can use a global variable to save search string and stop execution when search string changes.
IMPORTANT: You must set a timeout in each iteration so that function execution is paused and global variables are updated, as JavaScript is single-threaded.
Your code would look like this:
var searchString;
$("#search").keyup(function(e) {
// Update search string
searchString = $("#search").val().toLowerCase();
// Get items to be searched
var items = $(".lplist");
// Start searching!
goSearch(items, searchString, 0);
});
function goSearch(items, filter, iterator)
{
// Exit if search changed
if (searchString != filter) {
return;
}
// Exit if there are no items left
if (iterator >= items.length) {
return;
}
// Main logic goes here
var element = items[iterator];
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(filter, 0) != -1)
$(row).show();
else
$(row).hide();
// Schedule next iteration in 5 ms (tune time for better performance)
setTimeout(function() {
goSearch(items, filter, iterator + 1);
}, 5);
}
var sc = new stuCore();
function stuCore() {
this.readyPages = [];
this.once = true;
var self = this;
// gets called asynchronously
this.doPrepPage = function (page){
if(self.once == true){
// still gets executed every time, assignment fails
self.once = false;
doSomeStuffOnce();
}
};
this.addReadyPage = function (pageid) {
console.log("readypage called");
this.readyPages.push(pageid);
if (!$.inArray(pageid, self.readyPages) != -1) {
this.doPrepPage(pageid);
}
};
}
why does this assignment fail? I thought I knew the basics of js, but I'm stumped by this. And furthermore what would be a possible solution? call a constructor first and set the variable there?
EDIT:
gets called like this in some other script:
sc.addReadyPage(self.id);
The jQuery.inArray function will return the index in the containing array for the given value. Your script pushes pageid into this.readyPages before checking whether it exists in self.readyPages. this.readyPages and self.readyPages are the same array reference, so the result will always be zero or greater, so the condition that calls doPrepPage will never run.
You could try switching their order around:
this.addReadyPage = function (pageid) {
console.log("readypage called");
if ($.inArray(pageid, self.readyPages) != -1) {
this.readyPages.push(pageid);
this.doPrepPage(pageid);
}
};
(edit: Removed the additional !, thanks #chumkiu)
If I understand correctly you're calling this.doPrepPage as <insert variable name here>.doPrepPage?
If this is the case then your var self passes through to the anonymous function and is stored there, so everytime you call this.doPrepPage it takes the local variable of self.
Try setting self to a global variable, this way it will permanently modify self so each time this.doPrepPage is called it uses the updated variable.
I think this issue goes beyond typical variable scope and closure stuff, or maybe I'm an idiot. Here goes anyway...
I'm creating a bunch of objects on the fly in a jQuery plugin. The object look something like this
function WedgePath(canvas){
this.targetCanvas = canvas;
this.label;
this.logLabel = function(){ console.log(this.label) }
}
the jQuery plugin looks something like this
(function($) {
$.fn.myPlugin = function() {
return $(this).each(function() {
// Create Wedge Objects
for(var i = 1; i <= 30; i++){
var newWedge = new WedgePath(canvas);
newWedge.label = "my_wedge_"+i;
globalFunction(i, newWedge]);
}
});
}
})(jQuery);
So... the plugin creates a bunch of wedgeObjects, then calls 'globalFunction' for each one, passing in the latest WedgePath instance. Global function looks like this.
function globalFunction(indicator_id, pWedge){
var targetWedge = pWedge;
targetWedge.logLabel();
}
What happens next is that the console logs each wedges label correctly. However, I need a bit more complexity inside globalFunction. So it actually looks like this...
function globalFunction(indicator_id, pWedge){
var targetWedge = pWedge;
someSql = "SELECT * FROM myTable WHERE id = ?";
dbInterface.executeSql(someSql, [indicator_id], function(transaction, result){
targetWedge.logLabel();
})
}
There's a lot going on here so i'll explain. I'm using client side database storage (WebSQL i call it). 'dbInterface' an instance of a simple javascript object I created which handles the basics of interacting with a client side database [shown at the end of this question]. the executeSql method takes up to 4 arguments
The SQL String
an optional arguments array
an optional onSuccess handler
an optional onError handler (not used in this example)
What I need to happen is: When the WebSQL query has completed, it takes some of that data and manipulates some attribute of a particular wedge. But, when I call 'logLabel' on an instance of WedgePath inside the onSuccess handler, I get the label of the very last instance of WedgePath that was created way back in the plugin code.
Now I suspect that the problem lies in the var newWedge = new WedgePath(canvas); line. So I tried pushing each newWedge into an array, which I thought would prevent that line from replacing or overwriting the WedgePath instance at every iteration...
wedgeArray = [];
// Inside the plugin...
for(var i = 1; i <= 30; i++){
var newWedge = new WedgePath(canvas);
newWedge.label = "my_wedge_"+i;
wedgeArray.push(newWedge);
}
for(var i = 0; i < wedgeArray.length; i++){
wedgeArray[i].logLabel()
}
But again, I get the last instance of WedgePath to be created.
This is driving me nuts. I apologise for the length of the question but I wanted to be as clear as possible.
END
==============================================================
Also, here's the code for dbInterface object should it be relevant.
function DatabaseInterface(db){
var DB = db;
this.sql = function(sql, arr, pSuccessHandler, pErrorHandler){
successHandler = (pSuccessHandler) ? pSuccessHandler : this.defaultSuccessHandler;
errorHandler = (pErrorHandler) ? pErrorHandler : this.defaultErrorHandler;
DB.transaction(function(tx){
if(!arr || arr.length == 0){
tx.executeSql(sql, [], successHandler, errorHandler);
}else{
tx.executeSql(sql,arr, successHandler, errorHandler)
}
});
}
// ----------------------------------------------------------------
// A Default Error Handler
// ----------------------------------------------------------------
this.defaultErrorHandler = function(transaction, error){
// error.message is a human-readable string.
// error.code is a numeric error code
console.log('WebSQL Error: '+error.message+' (Code '+error.code+')');
// Handle errors here
var we_think_this_error_is_fatal = true;
if (we_think_this_error_is_fatal) return true;
return false;
}
// ----------------------------------------------------------------
// A Default Success Handler
// This doesn't do anything except log a success message
// ----------------------------------------------------------------
this.defaultSuccessHandler = function(transaction, results)
{
console.log("WebSQL Success. Default success handler. No action taken.");
}
}
I would guess that this is due to that the client side database storage runs asynchronous as an AJAX call would. This means that it doesn't stops the call chain in order to wait for a result from the invoked method.
As a result the javascript engine completes the for-loop before running the globalFunction.
To work around this you could perform the db query inside a closure.
function getDataForIndicatorAndRegion(indicator_id, region_id, pWedge){
return function (targetWedge) {
someSql = "SELECT dataRows.status FROM dataRows WHERE indicator_id = ? AND region_id = ?";
dbInterface.sql(someSql, [indicator_id, region_id], function(transaction, result) {
targetWedge.changeColor(randomHex());
});
}(pWedge);
}
This way you preserve pWedge for each execution. Since the second method is invoking it self and send what pWedge is right now as an argument.
EDIT: Updated the code from comments. And made a change to it. The callback function maybe shouldn't be self invoked. If it invoke it self the result of the function is passed as a argument. Also if it doesn't work, try passing the other arguments.
i suspect your problem is the modifed closure going on inside globalFunction:
function(transaction, result){
targetWedge.logLabel();
})
read this