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.
Related
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 using the async library's parallel function, but I am running into a weird problem. I loop through an array of objects to create an array of functions called people. People gets passed into async's parallel method to execute in parallel. However, when I take a look at the parameter "people" that actually get passed into the "printFunction" it is wrong. The people parameter is always the last object in my people array.
So in this example, the console log in printFunction would print
{ 'name' : 'jar jar binks' }
{ 'name' : 'jar jar binks' }
Code:
var people = [{ 'name' : 'obi wan kenobi'}, { 'name' : 'jar jar binks' }];
// Create an array of tasks to be processed.
for(var i = 0; i < people.length; i++) {
tasks.push( function(callback) {
setTimeout( makePrintFunction(people[i], callback), 200);
});
}
// Process those tasks.
async.parallel(tasks, function(err, stuff) {
// ...do some stuff with the returned array here.
});
Make Print Function:
function makePrintFunction(people, next) {
return validateAndTrackFlag(people, next);
}
function printFunction(people, next) {
console.log(people); // Always prints: { 'name' : 'jar jar binks' }
next(null, true)
}
Obviously this is not the real code, I just changed the names and objects.
Thanks for any help in advance.
This is not an async problem, it's a closure problem.
Try this:
for(var i = 0; i < people.length; i++) {
(function (i){
tasks.push( function(callback) {
setTimeout( makePrintFunction(people[i], callback), 200);
});
})(i)
}
Or this:
people.forEach (function (p){
tasks.push( function(callback) {
setTimeout( makePrintFunction(p, callback), 200);
});
});
You are dealing with classical scoping issue. Try this:
for(var i = 0; i < people.length; i++) {
(function(i) {
tasks.push( function(callback) {
setTimeout( makePrintFunction(people[i], callback), 200);
});
})(i);
}
or even better
var create_scope = function(i) {
tasks.push( function(callback) {
setTimeout( makePrintFunction(people[i], callback), 200);
});
};
for (var i = 0; i < people.length; i++) {
create_scope(i);
}
or the best:
people.forEach(function(person) {
tasks.push( function(callback) {
setTimeout( makePrintFunction(person, callback), 200);
});
});
This happens because for loop does not create a scope, i.e. when functions are fired i is the last index. Now functions do create scope, so inside anonymous function i is the expected value.
I'm trying to apply what I learned about callback functions in this post I made to extend to 3 functions, but am having some trouble getting things working. Can someone please help me understand how I can get these three functions to fire in sequence?
var yourCallback = function(args, second) {
var t = setTimeout(function() {
$('body').append(args);
}, 800);
second('3-');
}
var yourSecondCallback = function(args) {
var t = setTimeout(function() {
$('body').append(args);
}, 800);
}
function function1(args, callback, yourSecondCallback) {
$('body').append(args);
if (callback) {
callback('2-');
}
}
function1('1-' , yourCallback);
http://jsfiddle.net/loren_hibbard/WfKx2/3/
Thank you very much!
You need to nest the callbacks to get them to call in order.
var yourCallback = function(args, second) {
var t = setTimeout(function() {
$('body').append(args);
second('3-');
}, 800);
}
var yourSecondCallback = function(args) {
var t = setTimeout(function() {
$('body').append(args);
}, 800);
}
function function1(args, callback) {
$('body').append(args);
if (callback) {
callback('2-', yourSecondCallback);
}
}
function1('1-' , yourCallback);
Here's your altered fiddle
Your function names confuse me, so I'm just going to make some up to demonstrate a simple approach:
function1('-1', function(){
secondCallback(...);
thirdCallback(...);
...
});
Any reason a simple approach like that won't work for you?
Not sure exactly what you are trying to do here, but when you do this in function1:
callback('2-');
You are calling this method:
var yourCallback = function(args, second)
But you are not providing a value for second, so you get an error.
If your first argument and only is going to be an input for all the callbacks then this code can be used for unlimited arguments
var yourCallback = function(args, second) {
var t = setTimeout(function() {
$('body').append(args + ' first function');
}, 800);
}
var yourSecondCallback = function(args) {
var t = setTimeout(function() {
$('body').append(args + ' second function');
}, 800);
}
function function1(args) {
var callbacks = arguments.length - 1;
for (i = 1; i < arguments.length; i++) {
if (typeof(arguments[i] == 'function')) {
arguments[i](arguments[0]);
}
}
}
function1('1-', yourCallback, yourSecondCallback);
Fiddle - http://jsfiddle.net/8squF/
I have a function, mainMethod, that is calling three callback functions.
mainFunction the first callback function(one) will be called and the first parameter will be passed into it.
one will pass the second parameter into the second callback function (two).
two will pass the third parameter into the last callback function (three).
three will just log the last parameter that was passed into it.
function mainFunction(callback1, callback2, callback3){
var first_parameter = "ONE"
callback1(first_parameter);
}
function one(a){
console.log("one: " + a);
var second_parameter = "TWO"
two(second_parameter);
}
function two(b){
console.log("two: " + b);
var third_parameter = "THREE";
three(third_parameter);
}
function three(c){
console.log("three: " + c);
}
mainFunction(one, two, three);
I am using Cordova and Angular for a small webapp. Here is the implementation process:
LogsModel is created
queryLogs() is called
getLogsArray() is called – at this point, even in the function definition, console.log(this.logs['0']) returns undefined. I do not understand why.
LogsModel.prototype.logs = {};
LogsModel.prototype.getLogsArray = function (){
console.log(this.logs['0']);
return this.logs;
};
LogsModel.prototype.queryLogs = function (){
var self = this;
function getFromDb (tx){
tx.executeSql('SELECT * FROM DEMO',[],querySuccess,self.query_fail);
}
function querySuccess(tx,results){
var len = results.rows.length;
for(var i = 0; i < len; i++){
var log = {
verb: results.rows.item(i).verb
};
self.logs[i]= {
verb: results.rows.item(i).verb
};
}
}
this.db = window.openDatabase("Database","1.0","Cordova Demo",200000);
this.db.transaction(getFromDb,self.transaction_fail);
};
queryLogs is an asynchronous task. Call getLogsArray directly after starting the transaction and the array won't be populated yet.
Instead, use a callback which you invoke from the querySuccess function to continue processing (e.g. logging results).
I have to pass an array of functions to the async.js module for node.js.
The normal way from the docs would be:
async.parallel([
function(callback){
setTimeout(function(){
callback(null, 'one');
}, 200);
},
function(callback){
setTimeout(function(){
callback(null, 'two');
}, 100);
},
],
// optional callback
function(err, results){
});
I tried like this:
for(var i = 0; i < jsonData.length; i++)
{
...
o.url = serviceurl;
o.title = jsonData[i];
var ff = function(callback){
obj.loadService(o.title,o.url,callback);
}
callItems.push(ff(function(){return true;}));
}
async.parallel(
callItems,
// optional callback
function(err, results){
console.log('all calls called without any errors');
}
);
That runs through but the main callback isn't called. And so I can't say if all parallel calls are done.
What am I missing here?
It looks like the closures are improperly formed in the for loop. Try an external function that returns the value you're currently assigning to ff. Example:
for(var i = 0; i < jsonData.length; i++)
{
...
o.url = serviceurl;
o.title = jsonData[i];
var ff = makeCallbackFunc(obj, o.title, o.url);
callItems.push(ff(function () {return true;}));
}
function makeCallbackFunc(obj, title, url) {
return function (callback) {
obj.loadService(title, url, callback);
};
}
I'm a bit confused by what you are adding to callitems - namely the result of calling ff with the function parameter - it won't be a callback, it will execute right away.