Running query on collection items in nodejs - javascript

So I am stumped, and just cannot seem to grasp what is going on. I have a function that takes an array of orders.
var collection = [{ordernumber: 1, href: 'FileDetails.aspx?FileId=1234'},
{ordernumber: 2, href: 'FileDetails.aspx?FileId=1478'}];
var OrdersListToImport = [];
function loopOrders(collection, callback) {
for(var i = 0; i < collection.length; i++) {
console.log('processing order #: ' + collection[i].ordernumber);
var answersReturned = 0;
db.needsImported(collection[i].ordernumber, function(answer) {
if (answer) {
OrdersListToImport.push(collection[i]);
//console.log(collection[i]);
}
if (++answersReturned == collection.length) {
callback();
}
});
}
}
NeedsImported function as follows:
needsImported: function(ordernumber, callback) {
pool.query('Select controlnumber From orders Where ordernumber = ?', [ordernumber], function(err, result) {
if (!err) {
if (result.length == 0) {
callback(true);
}
else {
callback(false);
}
}
});
}
When inside the callback function of db.needsImported, collection[i] becomes undefined. It was driving me mad, so i made a little sample file to see if there was a reason I couldn't access an argument from inside a callback function. It works as expected, only pushing even numbers. here is the sample:
var nums = [];
var collection = [1,2,3,4,5,6,7,8,9,10];
displayValue(collection, function() {
});
console.log(nums);
function displayValue(col, callback) {
for(var i = 0; i < col.length; i++) {
sleep(col[i] * 500, function() {
console.log('Count: ' + col[i]);
if (col[i] % 2 == 0) {
nums.push(col[i]);
}
});
callback();
}
}
function sleep(time, callback) {
var stop = new Date().getTime();
while(new Date().getTime() < stop + time) {
;
}
callback();
}
I hope someone can help me to understand what I am doing wrong.

You might see more clearly what the problem is if you were to console.log(i) in the spot where you expect collection[i] to not be undefined.
What's going on in your for loop is you're iterating through your array and firing off multiple asynchronous tasks. When the loop is finished, the value of i is 2. i is a closed variable accessible to your callbacks, but remember the asynchronous timing here, your needsImported callbacks are all getting invoked after your for loop has completed, so naturally, collection[2] is undefined. To fix it you could just get rid of the index and use Array.forEach instead:
function loopOrders(collection, callback) {
var answersReturned = 0;
collection.forEach(function(item, index) {
needsImported(item.ordernumber, function(answer) {
console.log('index:', index);
if (answer) {
OrdersListToImport.push(item);
}
if (++answersReturned == collection.length) {
callback();
}
});
});
}
Note also that if you do need to keep track of the index in your callback, you can receive it as the second parameter in your forEach callback as I've done above.
One more note :) There's a significant flaw in this code:
needsImported: function(ordernumber, callback) {
pool.query('Select controlnumber From orders Where ordernumber = ?', [ordernumber], function(err, result) {
if (!err) {
if (result.length == 0) {
callback(true);
}
else {
callback(false);
}
}
});
}
If an err does occur, you're ignoring it, but worse than that, you're not invoking the callback. Confusing bugs will emerge from this. It's common practice in node to serve an err var as the first argument of a callback, so in a success scenario you would do this callback(null, true).

Related

making functions that need to be called in order and pass args modular

Hey I'm trying to understand functional programming, stay current etc.
I have this gigantic nested node function tha tI'm attempting to refactor into modules. Each function passes an argument to the next, some are async api calls. I've gotten as far as breaking it down into multiple functions but they're still not really modular as each one calls the next function.
I'm wondered if there's a design pattern to totally untangle them so that the names of the other functions don't appear in the functions but can be passed as callbacks or something. Or maybe that's not even appropriate in this case?
I wrote some simpler fake code that approximates my actual problem so you don't have to sift through endless irrelevant specifics:
function1Async();
function function1Async() {
var theArray = [];
var theData = "";
var theInfo = "";
Async.get( /* api */, function(error, data, response) {
if (error) {
function4(); // function call
}
theData = data[0].infos;
for (i in data) {
theInfo = theData[i].info;
theArray.push(theInfo);
}
function2Async(theArray); // function call
});
}
function function2Async(array) {
var theArray = [];
var theObject = {}
var arrayLength = array.length;
for (let i = 0; i < arrayLength; i++) {
search = '"' + array[i] + '"';
T.get(/* api */, {q: trend, search: 50}, function (error, data, response) {
if (error) {
theArray.push("error");
}
else {
theObject = {
"data": data
}
theArray.push(theObject);
}
if (theArray.length == arrayLength) {
function3(theArray); // function call
}
});
}
}
function function3 (array) {
var theArray = array;
if (theArray.includes("error") == true) {
function5(); // function call
}
else {
function4(theArray); // function call
}
}
function function4(array) {
var file = 'files/data.json';
jsonfile.writeFile(file, array, function(err) {
function5(); // function call
});
}
function function5() {
var file = 'files/data.json';
jsonfile.readFile(file, function(err, obj) {
return res.json(obj);
});
}
Feel free to chime in with any suggestions even if they're not totally related to the question. Like I said I'm just trying to understand this stuff altogether. Thanks.

Pass different versions of variable through asynchronous callback functions

I'm having trouble with some asynchronous database queries. I'm using the Async library for Node.js. I have some code forming an object before it hits the add() function. In my code, I have a loop the executes to create the object multiple times.
for(var i = 0; i < 10; i++){
var obj.id = i + '_idstring';
add(obj);
}
As you can see, each object is slight different. I need to perform a lookup in my database to see if the object already exists (1st waterfall function) and if it does not, add it to my database (2nd waterfall function). My issue is, loaObj.id always equals 10_idstring. Is this a problem with how I'm passing the object through the callbacks? How should I be handling this?
function add(loaObj) {
async.waterfall([
function(loaObj, callback) {
var sql = "";
connection.query(sql, function(error, results, fields) {
callback(error, results, loaObj);
});
},
function(results, loaObj, callback) {
if (results.length > 0) {
sendMessage();
} else {
var sql = "";
connection.query(sql, function(error, results, fields) {
callback(error, loaObj);
});
}
}
], function(err, loaObj) {
console.log(err);
if (err) {
sendMessage();
} else {
sendMessage();
}
});
}
Because you are using Object, which will be passed by "copy of a reference", then obj.id will be overridden with every loop and it will settle down to 10_idstring -where the loop stops-.
One simple solution is to pass new Object every time like below:
for(var i = 0; i < 10; i++){
add({id: `${i}_idstring`});
}

Asynchronously get generated array on function and pass it out of the box [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 6 years ago.
Problem resolved on Asynchronously solution to check data from database kinds of loop clause
with this below code i can generate simple array as jsonArray into checkUserMobileNumberAsEwallet function, but i can't pass it out of that to send inside client,
with socket.emit('syncContacts', accountNumbers) i get [] result on accountNumbers, but into if (success) { statement array successful created and pushed into accountNumbers array
socket.on('syncContacts', function (data) {
var accountNumbers = [];
for (var i = 0; i < data.length; i++) {
checkUserMobileNumberAsEwallet(data[i].mobileNumber, function (success) {
if (success) {
accountNumbers.push({ewalletNumber: this.mobileNumber});
console.log(accountNumbers);
}
}.bind({mobileNumber: data[i].mobileNumber}));
}
console.log(accountNumbers);
socket.emit('syncContacts', accountNumbers);
});
function checkUserMobileNumberAsEwallet(mobileNumber, callback) {
var mobileNumber = mobileNumber.substr(1, mobileNumber.length);
var query = "SELECT id FROM userEwallets WHERE ewalletNumber LIKE '%" + mobileNumber + "'";
connection.query(query, function (err, results) {
if (err) return callback(false);
if (results.length === 0)
return callback(false);
else {
return callback(true);
}
});
}
Updated after post comments:
socket.on('syncContacts', function (data) {
//console.log(accountNumbers);
//socket.emit('syncContacts', accountNumbers);
async.parallel(
[
function (callback) {
var accountNumbers = [];
for (var i = 0; i < data.length; i++) {
checkUserMobileNumberAsEwallet(data[i].mobileNumber, function (success) {
if (success) {
accountNumbers.push({ewalletNumber: this.mobileNumber});
console.log(accountNumbers);
}
}.bind({mobileNumber: data[i].mobileNumber}));
}
callback(success, accountNumbers);
}
],
function (success, results) {
console.log("results " + results.toString());
socket.emit('syncContacts', results);
});
});
function checkUserMobileNumberAsEwallet(mobileNumber, callback) {
var mobileNumber = mobileNumber.substr(1, mobileNumber.length);
var query = "SELECT id FROM userEwallets WHERE ewalletNumber LIKE '%" + mobileNumber + "'";
connection.query(query, function (err, results) {
if (err) return callback(false);
if (results.length === 0)
return callback(false);
else {
return callback(true);
}
});
}
Based on our code, it seems like checkUserMobileNumberAsEwallet is an asynchronous event, which accepts a function callback. That would mean that the for-loop would execute, which would queue up executions to checkUserMobileNumberAsEwallet. Immediately after the for-loop executes, console.log would correctly output the empty array accountNumbers and emit the event through the socket. Then the callback functions to each checkUserMobileNumberAsEwallet execution would begin to execute, and log the accountNumbers array which now has data.
This can be solved in a few ways, but likely the easiest and most readable would be to create Promises, and act on the Promises when they resolve. Personally I like the 'when' promise library, but many libraries could help to solve this problem. https://github.com/cujojs/when/blob/master/docs/api.md#whensettle
The problem is that you have no control over when the code inside the callback executes, and it ends up executing after you've already called socket.emit.
You can either a) use an async aggregator to call a callback for each successful mobile number and then, when all of those have finished, call the socket.emit call OR b) do something like what I did below. Alter your checkUserMobileNumberAsEwallet function to accept and verify an array of mobile numbers, and pass the successful numbers to your callback function
Try the following:
socket.on('syncContacts', function (data) {
var accountNumbers = [];
var mobileNumbers = [];
for (var i = 0; i < data.length; i++) {
mobileNumbers.push(data[i].mobileNumber);
}
checkUserMobileNumberAsEwallet(mobileNumbers, function (successfulNumbers) {
if (successfulNumbers.length > 0) {
for (var i = 0; i < successfulNumbers.length; i++) {
accountNumbers.push({ewalletNumber: successfulNumbers[i]});
}
socket.emit('syncContacts', accountNumbers);
}
}.bind({mobileNumbers: mobileNumbers}));
});

Return function value AFTER for-loop ends

I have the following function and I'm getting in the console false then true in that order. The true comes like 1-2 seconds after the false. I need the function to return false ONLY if no files were uploaded.
function uploadFeaturedImg()
{
var uploaded = false,
files = featuredImg.files;
if (files.length > 0)
{
for (var i = 0, len = files.length; i < len; i++)
{
var params = {
Key: 'tournament/image/'+files[i].name,
ContentType: files[i].type,
Body: files[i]
};
bucket.upload(params, function(err, data)
{
if (!err)
{
if (!uploaded) uploaded = true;
console.log(uploaded);
}
else
{
fileAlert(files[i].name);
}
});
}
}
console.log(uploaded);
return uploaded;
};
I ended up using another approach that worked for me better.
I'm also using .ajaxStop(): https://api.jquery.com/ajaxStop/. This basically lets me know when all files have been uploaded (if any).
There is an asynchrone problem here:
When you do your loop, you call for each iteration the function bucket.upload with a callback (it will be call when the action is finished). But your loop is ended when the last call yo bucket.upload is done, but NOT when all the callback are done.
So you return line is called BEFORE all the callback.
If you can understand that, you can also understand an asyncron function never return something (cause the function have to wait for something before end) but call a callback function when all it's done. (in this case the callback param)
To work fine you have to use a lib like async (Doc here)
Use it like that :
if (files.length > 0) {
async.each(
files,
function(file, cb) {
// This function will be called for each iteration
// ...
// Call your funciton here
bucket.upload(params, function(err, data) {
// When the callback is done, call the end for the iteration
cb();
}
},
function(err) {
// This function is called only when ALL callback are done.
callback(true); // All upload are done correctly
});
);
}
else {
// No files to upload
callback(false);
}
Well, it is always a bit complicated to deal with asynchronous code.
You should change your approach and pass a callback function in the signature of your method:
function uploadFeaturedImg(callback)
{
var uploaded = false,
files = featuredImg.files;
if (files.length > 0)
{
for (var i = 0, len = files.length; i < len; i++)
{
var params = {
Key: 'tournament/image/'+files[i].name,
ContentType: files[i].type,
Body: files[i]
};
bucket.upload(params, function(err, data)
{
if (!err)
{
if (!uploaded) uploaded = true;
if(callback) callback(); //Do what you were supposed to do with the result of your function in this callback function
}
else
{
fileAlert(files[i].name);
}
});
}
}
return uploaded;
};

NodeJS cannot create array with nested query

After a succesful query to Mongodb to get a list of news, for the news that have a link attached i search for link details in the database but after i set them inside the modified news object i cannot push it to an array. Why does this happen?
var newsarray = []
for(i=0;i<newsfound.length;i++){
if(!newsfound[i].link._id){
newsarray.push(newsfound[i])
} else {
var tempnew = newsfound[i];
db.findOne('links',{'_id':tempnew.link._id},function(err,linkdetails){
if(err){
console.log(err)
} else {
tempnew.linkdetails = linkdetails;
newsarray.push(tempnew)
}
})
}
}
console.log(newsarray)
The result of this is an array without the link containing news or if i try a few changes an array with the original news without the added details.
You can not use an asynchronous function inside a for loop. The reason is that the for loop gets executed before even any callback could come, and hence the scope gets messed up.
You should use recursive function (self calling) which will prevent the next async call before the previous is over.
var i = 0;
function fn() {
// your logic goes here,
// make an async function call or whatever. I have shown async in a timeout.
setTimeout(function () {
i += 1;
if (i < newsfound.length) {
fn();
} else {
// done
// print result
}
});
}
Update:
For your use case,
var newsarray = []
var i = 0;
function done() {
// done, use the result
console.log(newsarray);
}
function fn() {
if (!newsfound[i].link._id) {
newsarray.push(newsfound[i]);
i += 1;
if (i < newsfound.length) {
fn();
} else {
done();
}
} else {
var tempnew = newsfound[i];
db.findOne('links', {'_id':tempnew.link._id}, function(err, linkdetails){
if(err){
console.log(err)
} else {
tempnew.linkdetails = linkdetails;
newsarray.push(tempnew);
i += 1;
if (i < newsfound.length) {
fn();
} else {
done();
}
}
})
}
}

Categories

Resources