Node.js async iteratee with timeout - javascript

Bit new to node.js and javascript. Attempting to have my foreach call a function that in turn does a remote call. I'd like there to be a delay between each but can't seem to work out where to put the set timeout.
I know I have my setTimeout in the wrong place below but putting it in there as an example.
var likeRecommendation = function (recommendation, callback) {
context.Client.like(recommendation._id, function (error, data) {
recommendation['drupal_user_uid'] = context.message.uid;
recommendation['drupal_user_uuid'] = context.message.uuid;
if (error) return callback(new Error('Could not like recommendations'));
context.broker.publish('saves_swipes_publication', recommendation, function (err, publication) {
if (err) return callback(new Error('Could queue swipes to save'));
publication.on('error', console.error);
});
console.log('Liked!');
return callback()
});
}
async.forEach(context.recommendations, likeRecommendation, function (error) {
if (!error) return done(null);
done(new Error('Could not like recommendations'));
});
}

See timeout, warning one of your callback was missing and one other was not well placed.
var likeRecommendation = function (recommendation, callback) {
context.Client.like(recommendation._id, function (error, data) {
recommendation['drupal_user_uid'] = context.message.uid;
recommendation['drupal_user_uuid'] = context.message.uuid;
if (error)
setTimeout(function(){return callback(new Error('Could not like recommendations'))}, 100);
else {
context.broker.publish('saves_swipes_publication', recommendation, function (err, publication) {
if (err)
setTimeout(function(){return callback(new Error('Could queue swipes to save'))}, 100);
else
{
publication.on('error', console.error);
setTimeout(function(){ return callback();}, 100);
}
});
}
});
}
async.forEach(context.recommendations, likeRecommendation, function (error) {
if (!error) return done(null);
done(new Error('Could not like recommendations'));
});
}

The setTimeout will have to be placed at the end of the callback code.
Here is a possible solution (note that it doesn't use async.forEach)
var likeRecommendation = function (recommendation, callback) {
context.Client.like(recommendation._id, function (error, data) {
recommendation['drupal_user_uid'] = context.message.uid;
recommendation['drupal_user_uuid'] = context.message.uuid;
console.log(recommendation);
if (error) return done(new Error('Could not like recommendations'));
context.broker.publish('saves_swipes_publication', recommendation, function (err, publication) {
if (err) throw err
publication.on('error', console.error);
});
console.log('Liked!');
return callback()
});
}
function iterateWithTimeout(list, ctx, timeoutDuration) {
var currentIndex = 0;
(function invoke() {
list[currentIndex](ctx, function(error) {
if (!error) return done(null);
done(new Error('Could not like recommendations'));
setTimeout(function() {
if (++currentIndex < list.length) {
invoke();
}
}, timeoutDuration);
});
})();
}
// Interval is 1 second for now
iterateWithTimeout(context.recommendations, likeRecommendation, 1000);

So using "async.eachSeries" rather than "async.forEach" was the solution!
As always thanks for the help!

Related

Proper way to get the result of MySQL from Node.js function callback?

What is the proper way to get the result of this MySQL Query out of GetAllFarms and into a variable called err and farms? Sorry, doing a quick code try and coming from a different language.
var err, farms = GetAllFarms()
console.log("GetAllFarms:")
console.log(farms)
console.log(err)
function GetAllFarms(callback) {
query = db.query("SELECT * FROM farms ", function (err, result) {
console.log("DEBUG:QUERY//");
console.log(query.sql);
// console.log(result)
if (err) {
// console.log(err)
return callback(err, null)
} else {
// console.log(result)
return callback(null, result)
}
});
// db.end()
console.log("query")
console.log(query.result)
return query
}
Any help is much appreciated. Thanks
You have to decide wether you want to provide result via callback or with return. Don't mix them, it's confusable.
Callback approach
var err, farms = GetAllFarms()
console.log("GetAllFarms:")
console.log(farms)
console.log(err)
function GetAllFarms(callback) {
query = db.query("SELECT * FROM farms ", function (err, result) {
console.log("DEBUG:QUERY//");
console.log(query.sql);
// console.log(result)
if (err) {
// console.log(err)
return callback(err, null)
} else {
// console.log(result)
return callback(null, result)
}
});
// db.end()
console.log("query")
console.log(query.result)
}
// usage
GetAllFarms((error, result) => {
if (error) {
// handle error
}
// process result
})
Promise approach
var err, farms = GetAllFarms()
console.log("GetAllFarms:")
console.log(farms)
console.log(err)
function GetAllFarms() {
return new Promise((resolve, rejct) => {
db.query("SELECT * FROM farms ", function (err, result) {
console.log("DEBUG:QUERY//");
console.log(query.sql);
if (err) {
return reject(err)
} else {
return resolve(result)
}
});
});
}
// usage
(async () => {
const res = await GetAllFarms();
// or
GetAllFarms().then(/* ... */).catch(/* ... */);
})

mongodb nodejs store value outside connect

I need a way to store value outside mongoDB connect call:
read(object) {
let result
MongoClient.connect(this.url, function (err, db) {
if (err!=null){
result = err;
} else {
db.collection(object.collection).find(object.field).toArray(function(err, docs) {
assert.equal(err, null);
db.close();
result = docs;
});
}
});
return result
}
When i call this method, which is part of a class, return is called before result assignment, as normal.
Example: console.log(read(obj)) returns undefined
The idea is to store value in a variable and return might wait until connect terminate.
Is there any way to resolve this problem?
Without promise:
Call return inside find and err function:
read(object) {
let result
MongoClient.connect(this.url, function (err, db) {
if (err!=null){
result = err;
return result; //here
} else {
db.collection(object.collection).find(object.field).toArray(function(err, docs) {
assert.equal(err, null);
db.close();
result = docs;
return result; // here
});
}
});
}
Or set a timeout for return with enough time to wait for other process to end:
read(object) {
let result
MongoClient.connect(this.url, function (err, db) {
if (err!=null){
result = err;
} else {
db.collection(object.collection).find(object.field).toArray(function(err, docs) {
assert.equal(err, null);
db.close();
result = docs;
});
}
});
setTimeout(function(){ return result}, 3000); // 3secs
}
With Promise you can try the following:
function read(object) {
let result
return new Promise((resolve, reject) => {
MongoClient.connect(this.url, function (err, db) {
if (err!=null){
reject(err);
} else {
db.collection(object.collection).find(object.field).toArray(function(err, docs) {
db.close();
if (err) {
reject(err);
} else {
resolve(docs);
}
});
}
});
});
}
// then you can call the function and use the result like this
read(obj).then(docs => {
console.log(docs);
})
.catch(err => {
// handle error
console.log(err);
})

how to return array in Node.js from module

getting undefined all the time "main.js":
var dbAccess = require('../dao/dbAccess');
dbaInstance = new dbAccess();
var wordPool = dbaInstance.getWordPool();
console.log (wordPool);
and "dbAccess.js" contains:
var DatabaseAccess = function() {}
DatabaseAccess.prototype.getWordPool = function () {
RoundWord.find({},'words decoys', function(err, wordPoolFromDB) {
if (err) throw err;
//console.log(wordPoolFromDB); -working ok
return (wordPoolFromDB);
});
}
module.exports = DatabaseAccess;
why is it not working?
DatabaseAccess.prototype.getWordPool is not returning any result.
Since you are using an asynchronous function, you need do one of these things:
a) Take a callback as parameter and invoke the callback with a result
DatabaseAccess.prototype.getWordPool = function (cb) {
RoundWord.find({}, 'words decoys', function(err, results) {
if (err) {
return cb(err, null);
}
cb(null, results);
});
}
The callback convention is: cb(error, results...)
b) Use promises
DatabaseAccess.prototype.getWordPool = function () {
return RoundWord.find({}, 'words decoys', function (err, results) {
if (err) {
throw err; // however you might want to sanitize it
}
return results;
});
}
To consume this result you will need to do it as a promise
databaseAccess.getWordPool()
.catch(function (err) {
// process the error here
})
.then(function (results) {
// something with results
});
It will work if you change to this:
var dbAccess = require('../dao/dbAccess');
dbaInstance = new dbAccess();
dbaInstance.getWordPool(function(wordPool){console.log (wordPool);});
And:
var DatabaseAccess = function() {}
DatabaseAccess.prototype.getWordPool = function (cb) {
RoundWord.find({},'words decoys', function(err, wordPoolFromDB) {
if (err) throw err;
//console.log(wordPoolFromDB); -working ok
cb(wordPoolFromDB);
});
}
module.exports = DatabaseAccess;
If the function is Asynchronous you need to pass a callback to find to get the result:
DatabaseAccess.prototype.getWordPool = function (callback) {
RoundWord.find({},'words decoys', function(err, wordPoolFromDB) {
if (err) throw err;
callback(err, wordPoolFromDB);
});
}
and call it as follows in main:
dbaInstance.getWordPool(function (err, wordPool) {
console.log (wordPool);
// wordPool is only available inside this scope,
//unless assigned to another external variable
});
// cannot access wordPool here

returning data from node mssql execute functions

I'm using mssql(Microsoft SQL Server client for Node.js) package from npm.I'm trying to execute a stored procedure residing in my sql server database.Everything works fine.However what I want to do is return the recordsets so that i can export this to be used in other module.Below is what I'm trying to do.
function monthlyIceCreamSalesReport (scope){
var connObj = connConfig();
connObj.conn.connect(function(err){
if(err){
console.log(err);
return;
}
connObj.req.input('Month',4);
connObj.req.input('Year',2016);
connObj.req.execute('<myStoredProcedure>', function(err, recordsets, returnValue){
if(err){
console.log(err);
}
else {
console.log(recordsets[0]); // successfully receiving the value
}
connObj.conn.close();
});
});
console.log('check for recordsets', recordsets[0]); // undefined
return recordsets[0];
}
var sqlServerObj = {
monICSalesReport : monthlyIceCreamSalesReport,
};
module.exports = sqlServerObj;
As shown in the code snippet, since the value of recordsets[0] is undefined, exporting this function is of no use.
You can't return this way in async nature. You can get it by passing the callback function
Try to give a callback function like this
function monthlyIceCreamSalesReport(scope, callback) { // pass a callback to get value
var connObj = connConfig();
connObj.conn.connect(function(err) {
if (err) {
console.log(err);
return;
}
connObj.req.input('Month', 4);
connObj.req.input('Year', 2016);
connObj.req.execute('<myStoredProcedure>', function(err, recordsets, returnValue) {
if (err) {
console.log(err);
} else {
console.log(recordsets[0]);
connObj.conn.close();
return callback(null, recordsets[0]); //return as a callback here and get that value in callback from where you called this function
}
});
});
}
var sqlServerObj = {
monICSalesReport: monthlyIceCreamSalesReport,
};
module.exports = sqlServerObj;
Note: See the comment to understand the changes
recordsets[0] is undefinded, because is defined only in connObj.req.execute function scope. You may do this in this way:
function monthlyIceCreamSalesReport (scope, cb){
var connObj = connConfig();
connObj.conn.connect(function(err){
if(err){
console.log(err);
return cb(Error("Something wrong"));
}
connObj.req.input('Month',4);
connObj.req.input('Year',2016);
connObj.req.execute('<myStoredProcedure>', function(err, recordsets, returnValue){
if(err){
console.log(err);
connObj.conn.close();
return cb(Error("Something wrong"));
}
else {
console.log(recordsets[0]); // successfully receiving the value
connObj.conn.close();
return cb(recordsets[0]);
}
});
});
}
var sqlServerObj = {
monICSalesReport : monthlyIceCreamSalesReport,
};
module.exports = sqlServerObj;

how to make synchronous http calls within async.each in nodejs

I want to make http requests to an API-s to collect for each user it's data and insert into mongodb.
The problem I am having is, it is doing all the requests at once, and seems it gets stuck somewhere and I don't know what is going on.
Al thou I am using async library and add the request() method inside each iteration, and I dont know if this is the right way, here is the code:
function iterateThruAllStudents(from, to) {
Student.find({status: 'student'})
.populate('user')
.exec(function (err, students) {
if (err) {
throw err;
}
async.forEach(students, function iteratee(student, callback) {
if (student.worksnap.user != null) {
var options = {
url: 'https://api.worksnaps.com/api/projects/' + project_id + '/time_entries.xml?user_ids=' + student.worksnap.user.user_id + '&from_timestamp=' + from + '&to_timestamp=' + to,
headers: {
'Authorization': 'Basic bGhNSVJkVUFwOE1DS2loOFVyZkFyOENEZEhPSXdCdUlHdElWMHo0czo='
}
};
request(options, getTimeEntriesFromWorksnap);
}
callback(); // tell async that the iterator has completed
}, function (err) {
console.log('iterating done');
});
});
}
function getTimeEntriesFromWorksnap(error, response, body) {
console.log(response.statusCode);
if (!error && response.statusCode == 200) {
parser.parseString(body, function (err, results) {
var json_string = JSON.stringify(results.time_entries);
var timeEntries = JSON.parse(json_string);
_.forEach(timeEntries, function (timeEntry) {
_.forEach(timeEntry, function (item) {
saveTimeEntry(item);
});
});
});
}
}
function saveTimeEntry(item) {
Student.findOne({
'worksnap.user.user_id': item.user_id[0]
})
.populate('user')
.exec(function (err, student) {
if (err) {
throw err;
}
student.timeEntries.push(item);
student.save(function (err) {
if (err) {
console.log(err);
} else {
console.log('item inserted...');
}
});
});
}
var from = new Date(startDate).getTime() / 1000;
startDate.setDate(startDate.getDate() + 30);
var to = new Date(startDate).getTime() / 1000;
iterateThruAllStudents(from, to);
I am new to JavaScript, especially when dealing with async.
Any help?
Use Async.eachLimit() to make batched request to the api...Try this iterateThruAllStudents() function.
I already had same question before here
See tutorial of limiting here.
Though i am making the limit as 5 but you can do whatever you want(10,50 etc).
function iterateThruAllStudents(from, to) {
Student.find({status: 'student'})
.populate('user')
.exec(function (err, students) {
if (err) {
throw err;
}
async.eachLimit(students,5,function iteratee(student, callback) {
if (student.worksnap.user != null) {
var options = {
url: 'https://api.worksnaps.com/api/projects/' + project_id + '/time_entries.xml?user_ids=' + student.worksnap.user.user_id + '&from_timestamp=' + from + '&to_timestamp=' + to,
headers: {
'Authorization': 'Basic bGhNSVJkVUFwOE1DS2loOFVyZkFyOENEZEhPSXdCdUlHdElWMHo0czo='
}
};
request(options,getTimeEntriesFromWorksnap(callback));
}
}, function (err) {
console.log(err);
console.log('iterating done');
});
});
}
function getTimeEntriesFromWorksnap(cb) {
return function(error, response, body){
console.log(response.statusCode);
if (!error && response.statusCode == 200) {
parser.parseString(body, function (err, results) {
var json_string = JSON.stringify(results.time_entries);
var timeEntries = JSON.parse(json_string);
async.each(timeEntries,function(timeEntry,cb1){
async.each(timeEntry,function(item,cb2){
saveTimeEntry(item,cb2);
},function(err){
if(err)
cb1(err);
else
cb1();
})
},function(err){
if(err)
cb(err);
else
cb();
});
//_.forEach(timeEntries, function (timeEntry) {
// _.forEach(timeEntry, function (item) {
// saveTimeEntry(item);
// });
//});
});
}
cb(null);
}
}
function saveTimeEntry(item,cb2) {
Student.findOne({
'worksnap.user.user_id': item.user_id[0]
})
.populate('user')
.exec(function (err, student) {
if (err) {
return cb2(err);
}
student.timeEntries.push(item);
student.save(function (err) {
if (err) {
console.log(err);
//return cb2(err);//Do it if you wanna throw an error.
} else {
console.log('item inserted...');
}
cb2();
});
});
}
var from = new Date(startDate).getTime() / 1000;
startDate.setDate(startDate.getDate() + 30);
var to = new Date(startDate).getTime() / 1000;
iterateThruAllStudents(from, to);
In your example you missed iteratee param in the each method of async - iteratee(item, callback). Look at this example here.
You need to call callback each time inside your iteratee function to tell async continue doing its processing.
each(collection, iteratee, [callback])
collection - collection to iterate over.
iteratee(item, callback) - function to apply to each item in coll. The iteratee is passed a callback(err) which must be called once it has completed. If no error has occurred, the callback should be run without arguments or with an explicit null argument. The array index is not passed to the iteratee. If you need the index, use forEachOf.
callback(err) - Optional callback which is called when all iteratee functions have finished, or an error occurs.
If you need synchronous behavior, no probs! There is also eachSeries method with the same signature except every collection item will be iterated synchronously.
UPDATE:
Changes should be implemented:
Pass async callback:
request(options, getTimeEntriesFromWorksnap(callback));
Return necessary for request callback function:
function getTimeEntriesFromWorksnap(callback) {
return function(error, response, body) {
// ...
saveTimeEntry(item, callback);
// ...
}
}
Call callback only after record is saved in database:
function saveTimeEntry(item, callback) {
// ..
student.save(callback);
// ..
}
Refactor nested loops (not sure what timeEntries, timeEntry are, so use appropriate async method to iterate these data structures):
async.each(timeEntries, function (timeEntry, callback) {
async.each(timeEntry, function (item, callback) {
saveTimeEntry(item, callback);
}, callback);
}, callback);

Categories

Resources