Waiting for multiple callbacks in Node.js - node.js

I have a Node.js application where multiple funcions might be called, depending on several factors, but only one last function is called after the last callback.
This is a simplified version of what I got:
if(foo === bar){
function1(arg1, function(val1){
doWhatever(val1, function(){
res.end("Finished");
});
});
}else if(foo === baz){
function2(arg2, function(val2){
doWhatever(val2, function(){
res.end("Finished");
});
});
}else{
function3(arg3, function(val3){
doWhatever(val3, function(){
res.end("Finished");
});
});
}
And this is what im doing:
var finished = false;
if(foo === bar){
function1(arg1, function(val1){
result = val1;
finished = true;
});
}else if(foo === baz){
function2(arg2, function(val2){
result = val2;
finished = true;
});
}else{
function3(arg3, function(val3){
result = val3;
finished = true;
});
}
var id = setInterval(function(){
if(finished === true){
clearInterval(id);
doWhatever(result, function(){
res.end("Finished");
});
}
}, 100);
I guess this can be simplified by using promises, however im not sure how should I implement them.

You could also do it using when and promises, which IMHO is the easiest to read.
var promises = [];
if(x) {
var deferred1 = when.defer();
doSomethingAsync({ callback: deferred1.resolve });
promises.push(deferred1.promise);
} else if(y) {
var deferred2 = when.defer();
doSomethingAsync({ callback: deferred2.resolve });
promises.push(deferred2.promise);
} else if(z) {
var deferred3 = when.defer();
doSomethingAsync({ callback: deferred3.resolve });
promises.push(deferred3.promise);
}
when.all(promises).then(function () {
console.log('Finished Promises');
});

Here's one way with async series.
https://github.com/caolan/async#series
async.series([
function(callback){
if(foo === bar){
function1(arg1, function(val1){
callback(null, val1);
});
}else if(foo === baz){
function2(arg2, function(val2){
callback(null, val2);
});
}else{
function3(arg3, function(val3){
callback(null, val3);
});
}
}
], function(error, valArray){
doWhatever(valArray[0], function(){
res.end("Finished");
});
});

Here's using wait.for
https://github.com/luciotato/waitfor
//in a fiber
var result;
if(foo === bar){
result = wait.for(function1,arg1);
}else if(foo === baz){
result = wait.for(function2,arg2);
}else{
result = wait.for(function3,arg3);
};
doWhatever(result, function(){
res.end("Finished");
});
You need to be in a fiber (or generator) to use wait.for,
but, if you have a lot of callback hell,
wait.for is a good approach.

Related

MongoDB Fetch check if data exists

I am trying to find the best way to write this code. I fetch locations from a remote resource and need to check if there are any new locations present in the data, if there are I should add them to my database, if they are not new I just want to update them.
const http = require('http');
const timeout = 5000; //5 seconds
const MongoClient = require('mongodb').MongoClient;
// Database Name
const dbName = 'weatherApp';
const url = 'mongodb://localhost:27017';
// Connect using MongoClient
MongoClient.connect(url, function(err, client) {
if(err){
console.log(err);
return;
}
const locationsCollection = client.db(dbName).collection('locations');
(function fetchTemperatureLoop(){
console.log('Started http request..');
http.get('remote url..', function(resp){
var data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
if(data.isJson()){
var locations = JSON.parse(data).toArray();
(function locationsLoop(){
var location = locations.pop();
locationsCollection.findOne({location: location.location}, function(err, result){
if(err){
console.log(err);
return;
}
if(result){
//Exists
var measurements = result.measurements;
measurements.push({timestamp: +new Date, temperature: location.temperature})
locationsCollection.update({location: location.location}, {$set: {measurements: measurements}}, function(err){
if(err){
console.log(err);
return;
}
console.log('Added new temperature for location: ' + location.location);
continueLocationsLoop();
});
}else{
//Doesnt exist
location.measurements = [];
location.measurements.push({timestamp: +new Date, temperature: location.temperature});
locationsCollection.insert(location, function(err){
if(err){
console.log(err);
return;
}
console.log('Created new location: ' + location.location);
continueLocationsLoop();
});
}
});
function continueLocationsLoop(){
if(locations.length){
locationsLoop()
}else{
setTimeout(fetchTemperatureLoop, timeout);
}
}
})();
}
});
}).on("error", (err) => {
console.log("Error: " + err.message);
console.log("Continue anyways..");
setTimeout(fetchTemperatureLoop, timeout);
});
})();
});
String.prototype.isJson = function(){
try{
JSON.parse(this);
}catch(e){
return false;
}
return true;
}
Object.prototype.toArray = function(){
var arr = [];
for(var key in this){
if(this.hasOwnProperty(key)){
arr.push(this[key]);
}
}
return arr;
}
I really want to avoid using so many closures but I dont want to repeat myself either. Any help rewriting this code in an optimal way is much appriciated.
My main problem was illiterating through the locations and doing the calls to the database.

How Can I add A call back to a module

I create this small module to import in other node js modules.
However my callback function is giving me an error. "callback is not a function".
How can I make it work.
exports.saveConnection = function dao(action, callback){
pool.getConnection(function(err,conn){
if (err) {
console.log(err);
return callback('');
}
//create tables
var createTables = function(conn){
conn.release();
var rl = readline.createInterface({
input: fs.createReadStream('struct.sql'),
terminal: false
});
rl.on('line', function(chunk){
conn.query(chunk.toString('ascii'), function(err, sets, fields){
if(err){
console.log(err);
}else{
console.log("Table created");
}
});
});
console.log("Table created");
}
var findByPrimaryKey = conn.query("select * from user",function(err,rows){
conn.release();
if(err) {
console.log(err);
}
});
conn.on('error', function(err) {
console.log(err);
return;
});
callback(findByPrimaryKey);
});
};
When ever you are about to call callback you have to check if its a function or not. you can do that by following check
if (typeof(callback) === 'function') {
// call callback here
} else {
// just return because callback is not supplied
}
and when you call exports.saveConnection you have to supply callback as second param if you want to do something in that callback

Node js nested async call

I want to do async call from my getData function to getImage function but i am unable to get return data from getImage().Since the getData() does't wait for the completion of getImage(),as getImage() has further async db calls and therefore getData() always returns undefined.
What is the best way to do this instead doing nested callbacks?
var getData = function(id){
async.series([
function(callback){
var res = getImages(id);
callback(null, res);
}
],
// optional callback
function(err, results){
if (err) {
console.log("ERROR : " + err);
}else
{
console.log("Result: "+results);
}
});
}
var getImages = function(id){
async.series([
function(callback){
Image.find({id: id }).exec(
function(err, image) {
if (err) {
console.log(err);
callback(err, 0);
}else
{ console.log("Count: "+ image.length);
callback(null, image);
}
});
}
],
// optional callback
function(err, results){
if (err) {
console.log("ERROR : " + err);
}else
{
return results;
}
});
}
getData(1);
As you said you need to wait for getImages() to return, and you do that using promises.
Use any promise library, like q for instance:
var q = require('q')
...
var getImages = function(id){
var deferred = q.defer();
...
//do async logic that that evaluates some res obj you wish to return
db.find(..., function() {
deferred.resolve(res);
}
return deferred.promise;
}
Then, from getData(), you call it in the following matter:
getImages(id).then(
function(res) {
callback(null, res);
},
function(err) {
console.log("error:" + err);
}
);
As you are already using async - just use the waterfall functionality: https://github.com/caolan/async#waterfalltasks-callback
This way you will be able to run functions one after another and wait for the previous to finish, while still getting it's return value.

how to use yield to return the value in a function?

I want get the returned value from function selectpro, but the value always gets null.
This is the function selectpro:
exports.selectpro = function(){
var sql = "SELECT DISTINCT projectname FROM invoketable";
var projectname;
client.query(sql,function(err,result){
if(err) throw err;
projectname = result;
console.log('select projectname from db');
//console.log(projectname); //undefined
//return projectname;
});
console.log(projectname); //undefined
return projectname;
}
when to call the function selectpro:
var mysqlinsertp = require('./public/mysqloperate.js');
route.get('/readproject',function *(next){
var dataprodect = mysqlinsertp.selectpro();
this.body={status:200,ok:true,data:dataprodect};
console.log(this.body);
});
I get the return is undefined. I tried to use 'yield' but when I use mysqlinsertp.selectpro().next() to call the function, it returns 'next() is not defined'.
Your issue is timing the result.
You run client.query, which does the query and when its done, it will go to the callback function
function(err,result){
if(err) throw err;
projectname = result;
console.log('select projectname from db');
});
So the lines
console.log(projectname);
return projectname;
Are misplaced there.
Your choices are adding a callback function or using a Promise.
Callback function:
exports.selectpro = function(cb){
var sql = "SELECT DISTINCT projectname FROM invoketable";
var projectname;
client.query(sql,function(err,result) {
if(err) {
cb(err, null);
} else {
projectname = result;
console.log('select projectname from db');
cb(null, projectname);
}
});
};
And then call it like this:
var result;
xx.selectpro(function(err, res) {
if (err) throw err;
else result = res;
});
Or using a Promise:
exports.selectpro = function(){
return new Promise(function(resolve, reject) {
var sql = "SELECT DISTINCT projectname FROM invoketable";
var projectname;
client.query(sql,function(err,result){
if(err) {
reject(err);
} else {
projectname = result;
console.log('select projectname from db');
resolve(projectname);
}
});
});
};
And then call it using:
var result;
xx.selectpro.then(function(res) {
result = res;
});

How To Write a Mocha test for async waterfall

I have written a mocha test case for the async waterfall, "function2" is module which call for mongodb to get data, when i am trying to run the same code in node.js this async code works, but when i am do mocha test the "function2" call for mongodb throws error saying "[Error: Cannot determine state of server]".
i got to know the reason that the test is getting executed fast without waiting for mongodb to connect and return result.
can someone suggest me how to solve this problem. thanks in advance.
var function1 = require('../../method1');
var function2 = require('../../method2');
var function3 = require('../../method3');
describe('controller : ', function () {
it('should not return error', function (done) {
async.waterfall([
function(callback){
function1.method1(app.request, app.response, function(err,result) {
if(err){
callback(err,null);
}
else{
var var1 = result;
callback(null, var1);
}
});
},
function(var1, callback){
//Here I have a Mongodb Call
function2.method2(var1,function(err,result) {
if(err) {
callback(err,null);
}
else{
var var2= result;
var context = {
"c1": var1,
"c2": var2
};
callback(null, context);
}
});
},
function(context, callback){
function2.method2(context, function(err,result) {
if(err){
console.error(err);
callback(err,null);
}
else{
context.c3 = {};
result.forEach(function(e, i) {
for(var key in e) {
context.c3[key] = e[key];
}
});
callback(null, context);
}
});
},
function(context, callback){
should.exist(context);
}
],
function (err, result) {
should.not.exist(err);
should.exist(result);
});
});
});
I'm not sure but try to wait when connection (which use your module) is established.
before(function(done) {
connection.on('open', done);
});

Resources