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();
}
}
Related
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.
I have a function which contains another function call inside a for loop.
outerFunction(){
for (var j = 0; j < geoAddress.length; j++) {
innerFunction(j);
}
}
I need to wait till all the calls to innerFunction is complete. If I need parallel execution of these functions, how to achieve this in JavaScript?
Check out the async library.
https://www.npmjs.org/package/async
Check out the documentation on "whilst". It sounds like it does just what you need.
whilst(test, fn, callback)
var count = 0;
async.whilst(
function () { return count < 5; },
function (callback) {
count++;
setTimeout(callback, 1000);
},
function (err) {
// 5 seconds have passed
}
);
Edit - Doing Things the Node Way Using Q Promise Library
If you're using the Q promise library, then try the following:
outerFunction(){
var promises = [];
for (var j = 0; j < geoAddress.length; j++) {
deferreds.push(innerFunction(j));
}
Q.all(promises).then(function(){
// do things after your inner functions run
});
}
Even if you're not using this particular library, the principle is the same. One should have one's function return a promise or have it wrapped in a promise as in the Q.denodify method, push all calls to an array of promises, pass said array to your library's equivalent of .when() (jQuery) or .all() (Q Promise Library) and then use .then() to do things after all promises are resolved.
outerFunction() {
var done = 0;
function oneThreadDone() {
done++;
if (done === geoAddress.length) {
// do something when all done
}
}
for (var j = 0; j < geoAddress.length; j++) {
setTimeout(function() { innerFunction(j, oneThreadDone); }, 0);
}
}
and inside inner_function call oneThreadDone() function (reference passed throught param)
If you don't want to use external library for this you could make a scope object such as process which keeps track of how many innerFunction calls are still pending, and calls outer callback cb when it is finished.
The point with this is that you still get the benefits of async execution, but you just make sure that your code won't execute the next part until all innerFunction belonging to outerFunction are actually finished:
outerFunction(function() {
console.log("All done for outerFunction! - What you should do next?");
// This block is executed when all innerFunction calls are finished.
});
JavaScript:
// Example addresses
var geoAddress = ["Some address X", "Some address Y"];
var innerFunction = function(geoAddress, process) {
// Your work to be done is here...
// Now we use only setTimeout to demonstrate async method
setTimeout(function() {
console.log("innerFunction processing address: " + geoAddress);
// Ok, we update the process
process.done();
}, 500);
};
var outerFunction = function(cb) {
// Process object for tracking state of innerFunction executions
var process = {
// Total number of work ahead (number of innerFunction calls required).
count: geoAddress.length,
// Method which is triggered when some call of innerFunction finishes
done: function() {
// Decrease work pool
this.count--;
// Check if we are done & trigger a callback
if(this.count === 0) {
setTimeout(cb, 0);
}
}
};
for (var j = 0; j < geoAddress.length; j++) {
innerFunction(geoAddress[j], process);
}
};
// Testing our program
outerFunction(function() {
console.log("All done for outerFunction! - What you should do next?");
// This block is executed when all innerFunction calls are finished.
});
Output:
innerFunction processing address: Some address X
innerFunction processing address: Some address Y
All done for outerFunction! - What you should do next?
Here is js fiddle example
Cheers.
I am having problem with calling the generated functions in serial. I am using the async library and the code seems to work when there is no deep callback calling needed. When I add the real scenario it throws errors.
Here is the example which works, returns the array of 0 to 4:
Scrape.prototype.generatePageFunctions = function() {
var functionList = new Array(), self = this;
for (var i = 0; i < this.pageSet; i++) {
(function(i) {
functionList.push(function(cb) {
// Inner functions which will be called in seriers
var timeoutTime = parseInt(Math.random() * 5000 + 3000, 10);
setTimeout(function() {
self.setIndex(i);
//self.getSite(function)
cb(null, i);
}, timeoutTime);
});
})(i);
}
return functionList;
}
Scrape.prototype.run = function() {
var functionList = this.generatePageFunctions();
async.series(functionList, function(err, results) {
console.log('Job is completed ');
console.log(results);
});
}
Now adding the real scenario like downloading the site and then put in callback:
Scrape.prototype.generatePageFunctions = function() {
var functionList = new Array(), self = this;
for (var i = 0; i < this.pageSet; i++) {
(function(i) {
functionList.push(function(cb) {
// Inner functions which will be called in seriers
var timeoutTime = parseInt(Math.random() * 5000 + 3000, 10);
setTimeout(function() {
self.setIndex(i);
self.getSite(function(result) {
// Async callback to pass the data
cb(null, result);
});
}, timeoutTime);
});
})(i);
}
return functionList;
}
Error is like this, even if passing instead of result iterator variable i:
/home/risto/scrape/node_modules/async/lib/async.js:185
iterator(x.value, function (err, v) {
^
TypeError: Cannot read property 'value' of undefined
at /home/risto/scrape/node_modules/async/lib/async.js:185:23
at /home/risto/scrape/node_modules/async/lib/async.js:108:13
at /home/risto/scrape/node_modules/async/lib/async.js:119:25
at /home/risto/scrape/node_modules/async/lib/async.js:187:17
at /home/risto/scrape/node_modules/async/lib/async.js:491:34
at /home/risto/scrape/scraper/scrape.js:114:13
at /home/risto/scrape/scraper/scrape.js:64:16
at Object.<anonymous> (/home/risto/scrape/scraper/engines/google.js:58:12)
at Function.each (/home/risto/scrape/node_modules/cheerio/lib/api/utils.js:133:19)
at [object Object].each (/home/risto/scrape/node_modules/cheerio/lib/api/traversing.js:69:12)
// Edit
Only result which gets added into the complete callback is the first one, other functions are never called.
Also for information the functions return object literals if that does matter.
There is nothing wrong with your code. Creating a simple testcase shows that.
I created a mock:
Scrape = function() {
this.pageSet = 5;
}
Scrape.prototype.setIndex = function() {
}
Scrape.prototype.getSite = function(cb) {
cb('works');
}
and calling the run method it outputs the expected:
[ 'works', 'works', 'works', 'works', 'works' ]
So the problem is somewhere else. Have you tried to check the functionList variable in your run method?
Thank you #KARASZI István, all the code above is correct, the problem seemed in somewhere else. The deepest callback got called multiple times but the outer one got called only once.
I have a helper function which allows me to call functions in a different context. It's pretty simple:
function delegate(that, thatMethod)
{
return function() { return thatMethod.apply(that,arguments); }
}
This is ok if I wan't evaluate the variables at execution of the function, but sometimes I want to give the delegate-function values which are fixed at construction time.
Sample:
var callbacks = new Array();
for(var i = 0; i < 5; i++)
{
callbacks.push(delegate(window, function() { alert(i) }));
}
callbacks[3]();
In this case my expected behavior is that I get an alert(3) but because i is evaluated at execution we don't.
I know there is another delegate function which looks something like:
function delegatedd( that, thatMethod )
{
if(arguments.length > 2)
{
var _params = [];
for(var n = 2; n < arguments.length; ++n)
_params.push(arguments[n]);
return function() { return thatMethod.apply(that,_params); }
}
else
return function() { return thatMethod.call(that); }
}
But that doesn't help me either because I want to mix both methods. It can be written like that (first version of delegate used):
function(foo) {
return delegate(window, function() {
alert(foo);
});
}(i)
So i is construction time and everything else execution time.
The disadvatage of this is that it looks pretty ugly. Is there a better way to do it? Can I somehow hide it in a function?
Thanks
You can use the bind function:
var callbacks = new Array();
for(var i = 0; i < 5; i++)
{
//callbacks.push(delegate(window, function() { alert(i) }));
callbacks.push(function(n) { alert(n) }.bind(window, i);
}
callbacks[3]();
But bind is not implemented on IE(don't know about IE9), for how get it to work on IE see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind#Compatibility.
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.