javascript callback not pushing data up to the top of the calls - javascript

I am slowly learning how to use callbacks and I am running into trouble. I think the code below should work byt it is not.
From what I can tell it is not descending in to the recursive function as when it hit another 'tree' entry or outputting anything at the end.
One of the issues I ran into is the callback in the for loop. I was not sure that there is a better way to do it: I just used a counter to see if I was at the end of the loop before calling the callback.
Some guidance would be really appreciated.
(function () {
'use strict';
var objectsList = [];
function makeAJAXCall(hash, cb) {
$.ajaxSetup({
accept: 'application/vnd.github.raw',
dataType: 'jsonp'
});
$.ajax({
url: hash,
success: function (json) {
if (cb) {
cb(json);
}
},
error: function (error) {
console.error(error);
throw error;
}
});
}
function parseBlob(hash, cb) {
makeAJAXCall(hash, function (returnedJSON) { // no loop as only one entry
if (cb) {
cb(returnedJSON.data);
}
});
}
function complete(cb, loopLength, treeContents) {
concole.info(loopLength);
if (cb && loopLength === 0) {
objectsList.push(treeContents);
cb();
}
}
function parseTree(hash, treeName, cb) {
var treeContents = {'tree': treeName, 'blobs': []}, loopLength, i, entry;
var tree = 'https://api.github.com/repos/myusername/SVG-Shapes/git/trees/' + hash;
makeAJAXCall(tree, function (returnedJSON) {
loopLength = returnedJSON.data.tree.length;
for (i = 0; i < returnedJSON.data.tree.length; i += 1) {
entry = returnedJSON.data.tree[i];
if (entry.type === 'blob') {
if (entry.path.slice(-4) === '.svg') { // we only want the svg images not the ignore file and README etc
parseBlob(entry.url, function (json) {
treeContents.blobs.push(json.content);
loopLength -= 1;
complete(hash, loopLength, cb);
});
}
} else if (entry.type === 'tree') {
parseTree(entry.sha, entry.path, function () {console.info(objectsList);});
}
}
});
}
$(document).ready(function () {
parseTree('master', 'master', function () { // master to start at the top and work our way down
console.info(objectsList);
});
});
}());

Your recursion will not work properly because the variable where you are collecting blob data treeContents is a local variable of the recursive function so it's created new and destroyed on every call to parseTree(). It will not be accumulating data. You must create this variable outside the scope of the parseTree() function so a single instance of this variable can live from one call to the next and you can correctly accumulate data in it.
There are several ways to fix this:
You can pass the current state of treeContents into the parseTree() function
You can make the recursive part of the function be a local function that shares a common treeContents variable.
You can make the treeContents variable be a global variable.
The second is my choice here something like this:
function parseTree(topHash, topTreeName, topCb) {
var treeContents = {'tree': toptreeName, 'blobs': []};
function parse(hash, treeName, cb) {
var loopLength, i, entry;
var tree = 'https://api.github.com/repos/myusername/SVG-Shapes/git/trees/' + hash;
makeAJAXCall(tree, function (returnedJSON) {
loopLength = returnedJSON.data.tree.length;
for (i = 0; i < returnedJSON.data.tree.length; i += 1) {
entry = returnedJSON.data.tree[i];
if (entry.type === 'blob') {
if (entry.path.slice(-4) === '.svg') { // we only want the svg images not the ignore file and README etc
parseBlob(entry.url, function (json) {
treeContents.blobs.push(json.content);
loopLength -= 1;
complete(hash, loopLength, cb);
});
}
} else if (entry.type === 'tree') {
parse(entry.sha, entry.path, function () {console.info(objectsList);});
}
}
});
}
parse(topHash, topTreeName, topCb);
}
Assuming the ajax call is asynchronous, you will still need to find a way to know when you're done with all the parsing and call some function that you pass the treeContents data to because otherwise, that data is not available to any other function. It can't be simply returned from parseTree because of the asynch nature of the ajax calls.

Related

node.js for loop parallel array processing with only one callback

i want to make a for loop with a pattern-array and one object to check if the object is matched by one pattern. The patterns-array should processed parallel and if one of the patterns matches the object the onMatch()-callback-function should be called and cancel the other operations else the onNoMatch()-function should be called.
At the Moment it looks like this. I dont know where to put the onNoMatch()-function and sometimes there are multiple callbacks:
module.exports = function matchingPattern(patterns, object, onMatch, onNoMatch) {
for (key in patterns) {
if(patterns[key].param1 != object.param1) continue;
else if(patterns[key].param2 != object.param2) continue;
else if(patterns[key].param3 != object.param3) continue;
else {
onMatch(patterns[key].id);
}
}
}
//EDIT working solution
var arr = [1,2,3,4,5]
var async = require('async');
function matchPattern(pat, object, cb){
console.log(pat +' % '+ object);
if(pat % object == 0) cb(pat);
else cb();
}
function matchingPattern(patterns, object, onMatch, onNoMatch) {
async.each(patterns, function(pat, callback){
matchPattern(pat, object, function(match){
return callback(match);
});
}, function (res){
if(res) return onMatch(res);
return onNoMatch()
});
}
matchingPattern(arr, {2=2matches, 6=no matches}, function onMath(a){
console.log('onMatch('+a+')');
}, function onNoMatch(){
console.log('onNoMatch()');
});
I would personally prefer using async library as these sort of workflows can be easily handled using async.
var FOUND = {
code: 'Custom'
item: null,
pattern: null
};
function matchingPattern(patterns, object, onMatch, onNoMatch) {
async.each(patterns, function(pattern, callback){
// check pattern as per your business logic.
// assuming matchPattern is async
matchPattern(pattern, object, function(match){
if(match){
FOUND.item = object;
FOUND.pattern = pattern;
return callback(FOUND);
}else{
return callback();
}
});
},
function (error, result){
if(error){
if(error.code == 'Custom'){
// its not an error. We have used it as an error only.
return onMatch();
}else{
// handle error here.
}
}
// all items done and we have not found any pattern matching.
return onNoMatch();
});// end of async.each();
}// end of matchingPattern()

make async.waterfall start with argument from another function

I have bumped into a problem that I can't seem to solve. This is for a steam trading bot and it works well except for when two people trades with it at the same time because class_id and other_id are global variables and they will change in the middle of a trade if more than one is using it.
I tried defining the variables inside the last if statement but then get_class_id did not find the variables. Is there any way the async function can take item.classid and convert_id_64(offer.accountid_other) directly without defining them as variables? I appreciate any help.
var class_id
var other_id
function accept_all_trades(offers_recieved) {
offers_recieved.forEach( function(offer) {
if (offer.trade_offer_state == 1) {
if (typeof offer.items_to_give === "accept") {
offers.acceptOffer({tradeOfferId: offer.tradeofferid}, function(error, response) {
console.log('accepterat offer');
offer.items_to_receive.forEach(function(item) {
if (item.appid === '420') {
class_id = item.classid;
other_id = convert_id_64(offer.accountid_other);
console.log(class_id);
async.waterfall([get_class_id, get_stack, get_name, get_save], add_names);
}
});
});
}
}
});
}
function get_class_id(callback) {
var test = class_id
callback(null, test)
}
Update
I've changed the code to what ben suggested but still when I call get_class_id and try to print the id it is just a blank row in the console, any Ideas?
function get_class_id(callback) {
console.log(class_id);
var test = class_id;
callback(null, test)
}
The problem here is not aysnc.waterfall(). It's async calls (offers.acceptOffer(), get_class_id, get_stack, get_name, get_save, add_names) inside regular javascript forEach(). You need control-flow loops that can control the flow of those async calls. Here is the revised code using async.each():
function accept_all_trades(offers_recieved) {
async.each(offers_recieved, function(offer, eachCb) {
if (offer.trade_offer_state !== 1 || typeof offer.items_to_give !== "accept") {
return eachCb(null);
}
offers.acceptOffer({tradeOfferId: offer.tradeofferid}, function(error, response) {
console.log('accepterat offer');
async.each(offer.items_to_receive, function(item, eachCb) {
var class_id;
var other_id;
if (item.appid !== '420') {
return eachCb(null);
}
class_id = item.classid;
other_id = convert_id_64(offer.accountid_other);
console.log(class_id);
async.waterfall([
function(waterfallCb) {
var test = class_id;
console.log(class_id);
waterfallCb(null, test);
},
get_stack,
get_name,
get_save,
add_names
], eachCb);
}, function(err) {
console.log(err);
eachCb(null);
});
});
});
}

Integrating asynchronous mongo call within an inner forEach loop

I got two loops, the outer loops over the users and the inner one loops over the venueID's of each user. Within the inner loop I want to look up the venue and attach it to an array defined in the outer look (userItem). However because forEach is synchronous and the mongo database look up is asynchronous the result always remains empty. I've tried to integrate this answer but to no avail. How to do this?
ret = [];
users.forEach(function(user) {
var userItem = user.getSanitised('ADM');
userItem.venues = [];
var tmp = [];
userItem.adminVenueIds.forEach(function(adminVenueId){
tmp.push(function(callback) {
Venue.findOne({_id:adminVenueId}, function(error, venue) {
callback(null, venue.toObject());
});
});
});
async.parallel(userItem.venues, function(err, result) {
/* this code will run after all calls finished the job or
when any of the calls passes an error */
if (err)
return console.log(err);
userItem.venues.push(result);
});
ret.push(userItem);
});
Tried the following as well but doesn't work also
users.forEach(function(user) {
var userItem = [];
async.series({
setUserItem : function(callback)
{
userItem = user.getSanitised('ADM');
callback(null, 'OK');
},
setUserVenues : function(callback)
{
userItem.venues = [];
user.adminVenueIds.forEach(function(adminVenueId,index) {
Venue.findOne({_id:adminVenueId}, function(error, venue) {
userItem.venues.push(venue.toObject());
if((index+1) == user.adminVenueIds.length)
callback(null, 'OK');
});
});
}
},
function(error, results) {
if(error)
winston.error(error);
ret.push(userItem);
}
);
});
You could simply put an if statement (in your case put the conditional as the array length) then when the loop is done you could then make it continue doing its thing by calling a function to continue (or put your code in there, but it will start to look messy)
var ret = [];
var test = [];
for (var i = 0; i < 20; i++) {
for (var x = 0; x < 20; x++) {
setTimeout(function() {
test.push("Test"+x);
if (x === 20) {
finishIt();
}
}, 300)
}
}
function finishIt() {
console.log(test);
ret.push(test);
}
I think you might want to look into using Mongoose. It is a NodeJS application layer on top of MongoDB that provides a more SQL like experience.
http://mongoosejs.com
I ended up with the following solution. It's dirty but I guess that's just nodejs being nodejs.
users.forEach(function(user) {
var userItem = user.getSanitised('ADM');
userItem.venues = [];
user.adminVenueIds.forEach(function(adminVenueId) {
Venue.findOne({_id:adminVenueId}, function(error, venue) {
userItem.venues.push(venue.toObject());
});
});
(function(){
if(userItem.venues.length == user.adminVenueIds.length) {
ret.push(userItem);
} else {
setTimeout(arguments.callee, 30);
}
})();
});

Async Recursion with JavaScript and Node.js

This is probably a noob JavaScript question, but I'm looking to know if my solution to a problem I am having is 'correct'
I have created the following sample application that recreates my error:
Firstly in index.js
var processor = require('./fileProcessor/processor.js');
var container = {
source: "source.txt",
destination: "destination.txt"
};
new processor().process(container);
I create my container object which has the name of the source file and the name of the destination file. This is passed into the process function of the processor:
var fileProcessor = require('./fileProcessor.js');
module.exports = function Processor() {
this.process = function(container) {
var file = new fileProcessor();
if(container.finished === undefined) {
if(container.body === undefined) {
file.read(container, this.process);
} else {
file.write(container, this.process);
}
}
};
};
As you can see this calls the read and write functions passing in the container and the process function as the callback, the fileProcessor looks like this:
var fs = require('fs');
module.exports = function() {
this.read = function(container, callback) {
fs.readFile(container.source, function (err, data) {
if(err) throw err;
container.body = data;
callback(container);
});
};
this.write = function(container, callback) {
fs.writeFile(container.destination, container.body, function(err) {
if(err) {
return console.log(err);
}
container.finished = true;
callback(container);
});
};
};
In simple terms the processor calls file.read, which reads the file and calls back into the process function, which then calls the write function. However at the end of the write function an error is thrown:
callback(container);
^
TypeError: object is not a function
Obviously when passing in this.process to file.write(container, this.process); the this isn't the this I intend it to be!
If I update my processor by adding a processFunction variable:
var fileProcessor = require('./fileProcessor.js');
module.exports = function Processor() {
var processFunction = function(container) {
var file = new fileProcessor();
if(container.finished === undefined) {
if(container.body === undefined) {
file.read(container, processFunction);
} else {
file.write(container, processFunction);
}
}
};
this.process = function(container) {
processFunction(container);
};
};
Everything works fine. Is this a good way to do this or is there a better solution?
I think this is a fine way to do it. There is one possible modification that you might make. Since you are creating a new name in your scope just for the purpose of recursing, you could just name your function and refer to it by its name inside of the function.
module.exports = function Processor() {
this.process = function processFunction(container) {
var file = new fileProcessor();
if(container.finished === undefined) {
if(container.body === undefined) {
file.read(container, processFunction);
} else {
file.write(container, processFunction);
}
}
};
};
Then you can avoid creating a name (processFunction) that will be visible outside the function.
Take a look here for reference:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function#Named_function_expression

How to ensure that function a has been run before function b..?

I'm having some trouble with the following javascript code..
var returnValue = false;
function hasItem(id) {
//I want this entire function to run first
db.transaction(function(tx) {
tx.executeSql("SELECT * FROM library WHERE id == "+id,[],function(tx, results) {
returnvalue = results.rows.length>0;
},errorCB);
},errorCB,successCB);
//then this
return returnvalue;
}
But the sql-function appears to run in a separate thread, making the function return false all the time.. is there any way "to force a wait"..?
is there any way "to force a wait"..?
No. What you must do is change your hasItem function so that it accepts a callback that provides the information, instead of returning a value.
It's a bit tricky not knowing what your errorCB and successCB callbacks do, but something along these lines:
function hasItem(id, callback) {
var returnValue = false;
db.transaction(function(tx) {
tx.executeSql("SELECT * FROM library WHERE id == "+id,[],function(tx, results) {
returnValue = results.rows.length > 0;
},failed);
},failed,function() {
successCB();
callback(returnValue);
});
function failed() {
errorCB();
callback(null); // Or whatever you want to use to send back the failure
}
}
Then, instead of this
if (hasItem("foo")) {
// Do something knowing it has the item
}
else {
// Do something knowing it doesn't have the item
}
You use it like this:
hasItem("foo", function(flag) {
if (flag) {
// Do something knowing it has the item
}
else {
// Do something knowing it doesn't have the item
// (or the call failed)
}
});
If you want to tell, in the callback, whether the call failed:
hasItem("foo", function(flag) {
if (flag === null) {
// The call failed
}
else if (flag) {
// Do something knowing it has the item
}
else {
// Do something knowing it doesn't have the item
}
});

Categories