I need to wait for an ajax response inside a for loop. If I could I'd simply make a synchronous call instead of asynchronous, but I don't have that level of control: I'm using somebody else's API which in turn calls eBay's Javascript API.
Below are my two functions, actually methods on the same closure/object, with categoryStack and categoryMap in scope for each. In essence I'm trying to recursively build up a map, though I want to use a stack for management, rather than true recursion.
I've tried a few variations on setInterval/setTimeout but I always get one of two results: one iteration of the loop, or an endless loop. Note that m_eBay.getChildCategories specifies the second of the two functions below as a callback, and I have confirmed that I am getting there successfully.
function getChildCategories() {
categoryStack.push(-1);
while (categoryStack.length > 0) {
catId = categoryStack.pop();
m_eBay.getChildCategories({
'success':getChildCategoriesSuccess,
'failure':getChildCategoriesFailure},
{'siteid':0, 'CategoryID':catId, 'IncludeSelector':'ChildCategories'}
);
/*
use response from getChildCategoriesSuccess to reset categoryStack
*/
}
}
function getChildCategoriesSuccess(data){
if (data.categoryCount > 0) {
var categoryObjs = data.categoryArray.category;
for (var i=0, n=categoryObjs.length; i<n; i++) {
var catObj = categoryObjs[i];
if (catObj.categoryID != -1) { //skip root
categoryStack.push(catObj.categoryID);
categoryMap[catObj.categoryName] = catObj.categoryID;
}
}
}
}
Using asynchronous ajax you need to do something like:
function getChildCategories(onload) {
var categoryStack = [-1];
function doNextOrFinish() {
if (categoryStack.length) {
m_eBay.getChildCategories({
'success': function(data) {
if (data.categoryCount > 0) {
var categoryObjs = data.categoryArray.category;
for (var i=0, n=categoryObjs.length; i<n; i++) {
var catObj = categoryObjs[i];
if (catObj.categoryID != -1) { //skip root
categoryStack.push(catObj.categoryID);
categoryMap[catObj.categoryName] = catObj.categoryID;
}
}
}
doNextOrFinish();
},
'failure':getChildCategoriesFailure},
{'siteid':0, 'CategoryID':categoryStack.shift(), 'IncludeSelector':'ChildCategories'}
);
} else {
if (onload) onload();
}
}
doNextOrFinish();
}
Still uses recursion though.
Another solution to this problem is to use Arrows.
Related
My question is: How can I trigger a break; or continue; for a loop through a function that gets called? The thing is, I do not want to nest all possible conditions within a loop to avoid code complexity. I was thinking about some pseudo example:
var someObjects = [...] //array with some js objects
for (var i = 0; i < someObjects.length; i++) {
var element = someObjects[i];
doSomething(element);
}
function doSomething(object) {
//some code here
if (somethingIsDefined) {
doSomethingElse(object);
}
}
function doSomethingElse(anotherObject) {
//some more code here
if (somethingMatches) {
//here i would like to break the most upper loop
}
}
//someObjects will be processed
I know that it would be possible to introduce for example a boolean variable and check within the loop if it is true or false and depending on that, break; or continue;.But this - even if it is just one line - would increase the nesting. Do you see any possible solutions?
If you are using the Underscore library, which I recommend, you could write the following:
_.any(someObjects, doSomething);
function doSomething(object) {
//some code here
return somethingIsDefined &&
doSomethingElse(object);
}
function doSomethingElse(anotherObject) {
//some more code here
return somethingMatches;
}
I have a for loop that kicks off hundreds of async functions. Once all functions are done I need to run one last function but I can't seem to wrap my head around it knowing when all functions are complete.
I've tried promises but as soon as any of the functions in the loop resolve then my promise function completes.
for(var i = 0; i < someArray.length; i ++){
// these can take up to two seconds and have hundreds in the array
asyncFunction(someArray[i];
}
How can I tell once every function has completed?
An increment
You can add a callback which increments:
for (var i = 0; i < len; i++) {
asycFunction(someArray[i]);
asycFunction.done = function () {
if (i == someArray.length - 1) {
// Done with all stuff
}
};
}
A recursive approach
This type of approach is more liked by some developers but (might) take longer to execute because it waits for one to finish, to run another.
var limit = someArray.length, i = 0;
function do(i) {
asyncFunction(someArray[i]);
asyncFunction.done = function () [
if (i++ == someArray[i]) {
// All done!
} else { do(i); }
}
}
do(i++);
Promises
Promises aren't well supported at the moment but you can use a library. It will add a little bulk to your page for sure though.
A nice solution
(function (f,i) {
do(i++,f)
}(function (f,i) {
asyncFunction(someArray[i]);
asyncFunction.done = function () {
if (i++ === someArray.length - 1) {
// Done
} else { f(i) }
};
}, 0)
Many libraries have .all resolver:
jQuery
q
bluebird
and many more - https://promisesaplus.com/implementations
You can use them or learn their source code.
Assuming the code to be the body of function foo() :
function foo() {
return Promise.all(someArray.map(function(item) {
//other stuff here
return asyncFunction(item, /* other params here */);
}));
}
Or, if there's no other stuff to do, and no other params to pass :
function foo() {
return Promise.all(someArray.map(asyncFunction));
}
You can check number of response.
For every response you can increase counter value and if counter value same as someArray.length then you can assume all Async functions are done and can start next step.
How do you go about knowing when a For Loop is done iterating and attach a callback.
This is a sample loop of many loops within a function that inserts records into indexedDB.
if (Object.hasOwnProperty("Books")) {
for (var i = 0, j = Object["Books"].length; i < j; i++) {
server.Book.add({
title: Object["Books"][i].Cat,
content: Object["Books"][i]
});
}
}
I need to be able to know when each of the if statements loops are finished then attach a callback. All the loops are being fired asynchronously, and I need to run a function_final() just when all loops are finished not when they are fired.
EDIT
What I have tried so far :
InsertDB = {
addBook: function(Object) {
return $.Deferred(function() {
var self = this;
setTimeout(function() {
if (Object.hasOwnProperty("Book")) {
for (var i = 0, j = Object["Book"].length; i < j; i++) {
server.Book.add({
title: Object["Book"][i].id,
content: Object["Book"][i]
});
}
self.resolve();
}
}, 200);
});
},
addMagaz: function(Object) {
return $.Deferred(function() {
var self = this;
setTimeout(function() {
if (Object.hasOwnProperty("Magaz")) {
for (var i = 0, j = Object["Magaz"].length; i < j; i++) {
server.Magaz.add({
content: Object["Magaz"][i]
});
}
self.resolve();
}
}, 2000);
});
},
addHgh: function(Object) {
return $.Deferred(function() {
var self = this;
setTimeout(function() {
if (Object.hasOwnProperty("MYTVhighlights")) {
for (var i = 0, j = Object["MYTVhighlights"].length; i < j; i++) {
server.MYTVhighlights.add({
content: Object["MYTVhighlights"][i]
});
}
self.resolve();
}
}, 200);
});
}, ect...
then on a AJAX success callback :
success: function(data){
var Object = $.parseJSON(data);
$.when(InsertDB.addBook(Object),
InsertDB.addMagaz(Object),
InsertDB.addUser(Object),
InsertDB.addArticles(Object),
InsertDB.addHgh(Object),
InsertDB.addSomeC(Object),
InsertDB.addOtherC(Object)).done(final_func);
function final_func() {
window.location = 'page.html';
}
Here final_func is fired before looping ends..
Thanks
You can use JavaScript closures, just like this:
if (Object.hasOwnProperty("Books")) {
for (var i = 0, j = Object["Books"].length; i < j; i++) {
(function(currentBook)
server.Book.add({
title: currentBook.Cat,
content: currentBook
});
)(Object["Books"][i]);
}
function_final();
}
For more information about closures you can refer here.
Since you've said that server.Book.add() is asynchronous, you will need a method of knowing when that asynchronous operation is completed and you can then use that to build a system for knowing when all of them are done. So, the pivotal question (which I already asked as a comment earlier and you have not responded to) is how you can know when server.Book.add() is actually complete. If you're using an indexedDB, then somewhere inside that function, there is probably a request object that has an onsuccess and onerror methods that will tell you when that specific operation is done and that information needs to get surfaced out to server.Book.add() in some way, either as a completion callback or as a returned promise (those two options are how $.ajax() operates for it's asynchronous behavior.
Let's suppose that server.Book.add() returns a promise object that is resolved or rejected when the asychronous .add() operation is complete. If that was the case, then you could monitor the completion of all the operations in your loop like this:
if (obj.hasOwnProperty("Books")) {
var promises = [], p;
for (var i = 0, j = obj["Books"].length; i < j; i++) {
p = server.Book.add({
title: obj["Books"][i].Cat,
content: obj["Books"][i]
});
promises.push(p);
}
$.when.apply($, promises).done(function() {
// this is executed when all the promises returned by
// server.Book.add() have been resolved (e.g. completed)
}).error(function() {
// this is executed if any of the server.Book.add() calls
// had an error
});
}
Let's suppose that instead of server.Book.add() returning a promise, it has a couple callbacks for success and error conditions. Then, we could write the code like this:
if (obj.hasOwnProperty("Books")) {
var promises = [], p;
for (var i = 0, j = obj["Books"].length; i < j; i++) {
(function() {
var d = $.Deferred();
server.Book.add({
title: obj["Books"][i].Cat,
content: obj["Books"][i],
success: function() {
var args = Array.prototype.slice.call(arguments, 0);
d.resolve.apply(d, args);
},
error: function() {
var args = Array.prototype.slice.call(arguments, 0);
d.reject.apply(d, args);
}
});
promises.push(d.promise());
})();
}
$.when.apply($, promises).done(function() {
// this is executed when all the promises returned by
// server.Book.add() have been resolved (e.g. completed)
}).error(function() {
// this is executed if any of the server.Book.add() calls
// had an error
});
}
So, since you've not disclosed how server.Book.add() actually indicates its own completion, I can't say that either of these blocks of code work as is. This is meant to demonstrate how you solve this problem once you know how server.Book.add() communicates when it is complete.
Promises/Deferreds are not magic in any way. They don't know when an operation is completed unless that operation calls .resolve() or .reject() on a promise. So, in order to use promises, your async operations have to participate in using promises or you have to shim in a promise to an ordinary completion callback (as I've done in the second code block).
FYI, I also change your Object variable to obj because defining a variable named Object that conflicts with the built-in Object in the javascript language is a bad practice.
use jquery when functionality
$.when( function1 , function2 )
.then( myFunc, myFailure );
I'd write something like this in pure JS, consider it as pseudo code:
var totalForLoopsCount = 3; //Predict for loops count here
var forLoopsFinished = 0;
function finalFunction()
{
alert('All done!');
}
function forLoopFinished()
{
forLoopsFinished++;
if(forLoopsFinished == totalForLoopsCount)
{
finalFunction();
}
}
var length = 10; //The length of your array which you're iterating trough
for(var i=0;i<length;i++)
{
//Do anything
if(i == length-1)
{
forLoopFinished();
}
}
for(var i=0;i<length;i++)
{
//Do anything
if(i == length-1)
{
forLoopFinished();
}
}
for(var i=0;i<length;i++)
{
//Do anything
if(i == length-1)
{
forLoopFinished();
}
}
I am running into strange issue. This is regarding google Place search of type "textsearch". I am searching map for the results with a keyword and have a callback function to create result ("li" in html).
The problem is that google Place search api only gives 20 results for text search. To retrieve more results, we have to call pagination.nextPage(). This calls the same callBack function and gives more result.
So,
My code is
var request = {query: 'pizza in newyork'};
var service = new google.maps.places.PlacesService(map);
service.textSearch(request, callBack1);
function callBack1(results, status,pagination) {
for (var i = 0; i < results.length; i++) {
var place = results[i];
//add place as li
}
if (pagination.hasNextPage) {
pagination.nextPage();
}
doOtherOperation();
}
function doOtherOperation() {
//do manipulations on "li" which are created from callBack1
}
The problem is that doOtherOperation() starts executing before callBack1() completes execution.
Anybody can help? how to make sure that callBack1 will executed fully (including recursive calls by pagination.nextPage())?
Not an expert, but looks like a small logic flaw, so I will do it this way:
if (pagination.hasNextPage) {
pagination.nextPage();
}
else
{
doOtherOperation();
}
Otherwise doOtherOperation(); gets called each time regardless of the fact that you have to go trough nextPage again.
Hope it helps
try to wrap doOtherOperation() with setTimeout(function() { /* */}, 0)
UPDATE... how about that?
var flag = false;
function callBack1(results, status,pagination) {
/* since callback1 is the same callback for textSearch() and nextPage() -
you need to call dootherOperation at the beginning of statement */
if (flag) {
doOtherOperation();
}
for (var i = 0; i < results.length; i++) {
var place = results[i];
//add place as li
}
if (pagination.hasNextPage) {
flag = true;
pagination.nextPage();
}
else {
flag = false;
}
}
I need to pause a for loop and not continue until I specify. For each item in the array that I'm looping through, I run some code that runs an operation on a separate device, and I need to wait until that operation is finished before looping to the next item in the array.
Fortunately, that code/operation is a cursor and features an after: section.
However, it's been running the entire for loop instantly, which I need to prevent. Is there any way to prevent the loop from continuing until specified? Or perhaps a different type of loop or something that I should use?
My first (poor) idea was to make a while-loop within the for-loop that ran continuously, until the after: portion of the cursor set a boolean to true. This just locked up the browser :( As I feared it would.
Anything I can do? I'm fairly new to javascript. I've been enjoying my current project though.
Here's the while-loop attempt. I know it's running the entire loop immediately because the dataCounter goes from 1 to 3 (two items in the array currently) instantly:
if(years.length>0){
var dataCounter = 1;
var continueLoop;
for(var i=0;i<years.length;i++){
continueLoop = false;
baja.Ord.make(historyName+"?period=timeRange;start="+years[i][1].encodeToString()+";end="+years[i][2].encodeToString()+"|bql:select timestamp, sum|bql:historyFunc:HistoryRollup.rollup(history:RollupInterval 'hourly')").get(
{
ok: function (result) {
// Iterate through all of the Columns
baja.iterate(result.getColumns(), function (c) {
baja.outln("Column display name: " + c.getDisplayName());
});
},
cursor: {
before: function () {
baja.outln("Called just before iterating through the Cursor");
counter=0;
data[dataCounter] = [];
baja.outln("just made data["+dataCounter+"]");
},
after: function () {
baja.outln("Called just after iterating through the Cursor");
continueLoop = true;
},
each: function () {
if(counter>=data[0].length) {
var dateA = data[dataCounter][counter-1][0];
dateA += 3600000;
}
else {
var dateA = data[0][counter][0];
}
var value=this.get("sum").encodeToString();
var valueNumber=Number(value);
data[dataCounter][counter] = [dateA,valueNumber];
counter++;
},
limit: 744, // Specify optional limit on the number of records (defaults to 10)2147483647
offset: 0 // Specify optional record offset (defaults to 0)
}
})
while(continueLoop = false){
var test = 1;
baja.outln("halp");
}
dataCounter++;
}
}
Do not use a for loop to loop on each element. You need, in the after: to remember which element of the array you've just done and then move to the next one.
Something like this :
var myArray = [1, 2, 3, 4]
function handleElem(index) {
module.sendCommand({
..., // whatever the options are for your module
after: function() {
if(index+1 == myArray.length) {
return false; // no more elem in the array
} else {
handleElem(index+1)} // the after section
}
});
}
handleElem(0);
I assumed that you call a function with some options (like you would for $.ajax()) and that the after() section is a function called at the end of your process (like success() for $.ajax())
If the "module" you call is not properly ended in the after() callback you could use setTimeout() to launch the process on the next element with a delay
EDIT: With your real code it would be something like this :
function handleElem(index) {
baja.Ord.make("...start="+years[index][1].encodeToString()+ "...").get(
{
ok: ...
after: function() {
if(index+1 == years.length) {
return false; // no more elem in the array
} else {
handleElem(index+1)} // the after section
}
}
});
}