Callback hell in Node.js - javascript

I am experiencing a "callback hell" situation in Node.js.
Basically what I want is:
Read data from a static json file (local) --> query MongoDB to get two records from two separate collections --> compare the returned data -> add the result after compare into result object --> go to next step in the loop --> repeat.
Please review the code and let me know where is the problem.
jsonfile.readFile(file, function(err, staticData) {
if(err){
console.log("Error while loading Tower Details from Static Data " + err);
}
else{
var staticD = staticData.Teams;
var l = staticData.Teams.length;
// console.log('*******************Getting Tower Level Data from Static File*******************');
//console.log('*******************Tower Name received is ******************* ' + staticData.Tower);
if(counter == l){
console.log('Inside the couneter loop');
res.json(testObject);
}
for ( var i = 0 ; i<l; i++){
var trackName = staticD[i].name
console.log('Counter--------->>>>' + counter);
//console.log("Team name " + staticD[i].name);
++counter;
for (var j = 0 ; j<staticD[i].applications.length;j++){
//var RObj;
//var AObj;
//console.log("Application Name " + staticD[i].applications[j]);
var applicationName = staticD[i].applications[j];
var test = new Object();
test.data = [];
var resultSet;
var response = reference.find({'appname' : applicationName , 'track' : trackName }).sort({'_id': -1});
var promise = response.exec();
var alertT = alert.find({'appname' : applicationName , 'track' : trackName }).sort({'_id': -1}).limit(1);
var promise1 = alertT.exec();
promise.then(function allRefRecords (recordAlerts){
if(recordAlerts.length >0){
//console.log('Ref Length' + recordAlerts.length);
recordAlerts.forEach(function refRecord(R){
testObject.data.testInfra.push(R);
//console.log('testObject' + testObject.data.testInfra);
});
}
});
promise1.then(function allAlertsRecords (alerts){
if(alerts.length > 0){
alerts.forEach(function refRecord(a){
// console.log('a' + a)
testObject.data.testCustom.push(a);
//console.log('testObject' + testObject.data.testCustom);
// res.json(testObject);
});
}
})
.then(function(){
resultSet = compareData(testObject.data.testCustom,testObject.data.testInfra);
test.data.push(resultSet);
})
.then(function(){
res.json(test);
});
}
}
}
});
});

Don't nest functions, give them names and place them at the top level
of your program. Use function hoisting to your advantage to move
functions 'below the fold'. Handle every single error in every one
of your callbacks and use a linter like standard to help you with
this. Create reusable functions and place them in a module to reduce
the cognitive load required to understand your code. Splitting your
code into small pieces like this also helps you handle errors, write
tests, forces you to create a stable and documented public API for
your code, and helps with refactoring.
Source : http://callbackhell.com/
It is possible to avoid callback hell with ASYNC, with PROMISES, with DESIGNS, and many other ways...
But 99% of time, design is the finest (imho) and you don't need other things.
Some links :
How to Elegantly Solve the Callback Hell
Avoiding Callback Hell in Node.js
Remember that callback hell is not a fatality ;)

Some tips to prevent a further appearance of Callback Hell you can browse next libraries:
Async.js: you can execute functions in series without nesting them.
Bluebird: asynchronous logic will be more manageable with mapping and enqueueing.
Q: reveals the concept of promise to manage the nested calls effortlessly.

Related

Parse..com cloud code: Trying to update user's column but nothing happens

i have a background job that is trying to update user balance with a new balance if they won the league
This functions are helper functions which are being called properly but "query.first" in updateUserEarnings function and "then" function inside updateWinningsHelper are never called, at least nothing is shown in log, . And yes I am user masterkey.
var updateUserEarnings = function (User,prizeValue){
//var user = new Parse.User();
console.log("updateUserEarnings Called");
console.log("User Id: " + User.id);//Shown in log fi
console.log("PrizeValue " + prizeValue);//Shown in log file
var query = new Parse.Query(Parse.User);
query.equalTo("objectId",User.id);
return query.first({
success: function(result){
console.log("updateUserEarnings success");//Never Reached here
var balance;
if (result.get("cashBalance")){
balance = result.get("cashBalance");
}else{
balance = 0;
}
console.log("Current Balance "+ balance + " for "+result.get("DisplayUsername"));
balance = balance + prizeValue;
result.set("cashBalance",balance);
console.log("User " + result.get("DisplayUsername") + " Balance Updated : " + balance);
return result
},error:function(error){
console.log("user update error "+error);//never reached here as well
return error;
}
})
}
var updateWinningsHelper = function(index,lastIndex,record){
var winningObj = record.winningObject;
console.log("Winning object "+ JSON.stringify(winningObj));//Shown in log
console.log("Index : "+index + " lastIndex : "+lastIndex);//Shown in log
if (index < lastIndex){
updateUserEarnings(winningObj.get("User"), winningObj.get("League").get("PrizeValue")).then(
function(user){
console.log("Inside then function");//Never Reached
saveAllObjects.push(user);
},
function(error){
console.log("Error Happened "+error );//never reached
}
);
winningObj.set("LeagueWon",true);
winningObj.set("isRedeemed",true);
}else{
winningObj.set("LeagueWon",false);
}
index++;
winningObj.set("Rank",index);
return winningObj;
};
Any help is appreciated
Turns out you don't need to query again if you already have User pointer from main query. I included User column with the main query and I can use regular set function to that user.
all i did was
var updateUserEarnings = function (User,prizeValue){
//var user = new Parse.User();
User.set("cashBalance", User.get("cashBalance")+prizeValue);
}
hope this helps anyone in the future and not waste 5 hours like me.
Glad you found the problem!
While on the topic, you may want to look into removing any confusion caused by mixing javascript programming paradigms. You use the old callback paradigm as well as the newer Promises for asynchronous operations, which is a mess to debug.
Parse Engineering on JS Promises
I looked at your code for a fair bit of time before realizing how you were trying to perform the query's old-style callback, then return a promise from the query to the calling function, and then use the promise callback in the update helper.

asychronous function in loop javascript nodejs

I need to scan the trip array and calculate the travel time between the current trip with each trip in the array and select the shortest one. For calculation i need to send google maps api call.
I am very confused about the asynchronous callback function .
Can anyone help me on this how to send api call within for loop and check the results and continue?
Thank you.
The trips are in my array list;
Array :
array=[trip1,trip2, trip3,....];
JS :
function assigntrips(array){
var triplist = [];
for(var i=0; i< array.length; i++){
var fstnode = array[i];
for(var j=i+1; j<array.length; j++){
//here i want to get the response from google api and decide if i want to choose the trip.
if not the for loop continues and send another api call.
}
}
}
function apicall(inputi, cb){
var destination_lat = 40.689648;
var destination_long = -73.981440;
var origin_lat = array[inputi].des_lat;
var origin_long = array[inputi].des_long;
var departure_time = 'now';
var options = {
host: 'maps.googleapis.com',
path: '/maps/api/distancematrix/json?origins='+ origin_lat +','+origin_long+ '&destinations=' + office_lat + ',' + office_long + '&mode=TRANSIT&departure_time=1399399424&language=en-US&sensor=false'
}
http.get(options).on('response',function(response){
var data = '';
response.on('data',function(chunk){
data += chunk;
});
response.on('end',function(){
var json = JSON.parse(data);
console.log(json);
var ttltimereturnoffice = json.rows[0].elements[0].duration.text;
//var node = new Node(array[i],null, triptime,0,ttltimereturnoffice,false);
//tripbylvtime.push(node);
cb(ttltimereturnoffice + '\t' + inputi);
});
});
}
You cannot check the results in the loop. The loop is in the past, the callbacks happen in the future - you can't change that. There are only two things you can do, and one is an abstraction of the other:
1) You can create your callback in such a manner that it will collect the results and compare them when all are present.
2) You can use promises to do the same thing.
The #1 approach would look something like this (while modifying the cb call in your code appropriately):
var results = [];
function cb(index, ttltimereturnoffice) {
results.push([index, ttltimereturnoffice]);
if (results.length == array.length) {
// we have all the results; find the best one, display, do whatever
}
}
I don't quite know what library you are using, and if it supports promises, but if http.get returns a promise, you can do #2 by collecting the promises into an array, then using the promise library's all or when or similar to attach a callback on all gets being done.

Meteor: Lazyload, load after rendering. Best practise

i have a Meteor Application which is very "slow" as there are a lot of API-Calls.
What i try to do is to break apart the loading/calls.
What i just did is:
i have loading template via iron-router
i waitOn for the first API-Call has finished
then i start the next API-calls in the Template.myTemplate.rendered - function
This was already a big benefit for the speed of my Application, but i want to break it up even more as the second call is in fact more like 5-25 API-calls.
So what i try to do now is inside the rendered function is a self-calling function which calls itself as long as there are no more to do and saves the response inside a session. (Until now it just rewrites, but even to this point i can´t get)
Template.detail.rendered = function(){
//comma separated list of numbers for the API-Call
var cats = $(this.find(".extra")).attr('data-extra').split(',');
var shop = $(this.find(".extra")).attr('data-shop');
var counter = 0;
var callExtras = function(_counter){
var obj = {
categories : [cats[_counter]],
shop : shop
};
if(_counter <= cats.length){
Meteor.subscribe('extra', obj,function(result){
//TODO dickes todo... nochmal nachdenken und recherchieren
//console.log(_counter);
Session.set('extra',Extra.find('extra').fetch()[0].results);
counter++;
callExtras(counter);
});
}
};
callExtras(counter);
Session.set('loading_msg', '' );
};
Now i have again problems with my reactive parts of the app desscribed here - Meteor: iron-router => waitOn without subscribe As i can´t find a proper way to update my client-side per user base collection. Also in the docs it is described the publish method also creates a new collection. (The new document´s ID) here - http://docs.meteor.com/#/full/publish_added
here is the publish from server
Meteor.publish('extra', function(obj){
var that = this;
Meteor.call('extra', obj, function(error, result){
if (result){
//console.log(result);
that.added("extra", "extra", {results: result});
//that.changed('extra','extra',{results: result});
that.ready();
} else {
//that.ready();
}
});
});
So my question is: Is there from scratch a better way to structuring my code means solving the problem somehow different? If not how can i achive it the cleanest way? Because for my understanding this is just strange way to do it.
EDIT:
For example.
Can i do a per-user-collection (maybe only client-side like now) and push data from the server and just subscribe to this collection? But then how can i check when the async API-Call has finshed to start the next round. So the view gets data piece by piece. I am just confused right now.
My fault was simple as i thaught: You don´t need to use subscribe.
I just added "error,result" in the callback of Meteor.call
Only "result" leads to the result is always undefined.
var cats = $(this.find(".extra")).attr('data-extra').split(',');
var shop = $(this.find(".extra")).attr('data-shop');
var counter = 0;
var callExtras = function(_counter){
var obj = {
categories : [cats[_counter]],
shop : shop
};
if(_counter <= cats.length){
Meteor.call('extra', obj,function(error,result){
var actual_session = Session.get('extra');
if(actual_session === false){
actual_session = [];
}
actual_session = actual_session.concat(result);
Session.set('extra',actual_session);
counter++;
callExtras(counter);
});
}
};
callExtras(counter);
Then in the template helper
"extra" : function(){
return Session.get('extra');
},

Assemble paginated ajax data in a Bacon FRP stream

I'm learning FRP using Bacon.js, and would like to assemble data from a paginated API in a stream.
The module that uses the data has a consumption API like this:
// UI module, displays unicorns as they arrive
beautifulUnicorns.property.onValue(function(allUnicorns){
console.log("Got "+ allUnicorns.length +" Unicorns");
// ... some real display work
});
The module that assembles the data requests sequential pages from an API and pushes onto the stream every time it gets a new data set:
// beautifulUnicorns module
var curPage = 1
var stream = new Bacon.Bus()
var property = stream.toProperty()
var property.onValue(function(){}) # You have to add an empty subscriber, otherwise future onValues will not receive the initial value. https://github.com/baconjs/bacon.js/wiki/FAQ#why-isnt-my-property-updated
var allUnicorns = [] // !!! stateful list of all unicorns ever received. Is this idiomatic for FRP?
var getNextPage = function(){
/* get data for subsequent pages.
Skipping for clarity */
}
var gotNextPage = function (resp) {
Array.prototype.push.apply(allUnicorns, resp) // just adds the responses to the existing array reference
stream.push(allUnicorns)
curPage++
if (curPage <= pageLimit) { getNextPage() }
}
How do I subscribe to the stream in a way that provides me a full list of all unicorns ever received? Is this flatMap or similar? I don't think I need a new stream out of it, but I don't know. I'm sorry, I'm new to the FRP way of thinking. To be clear, assembling the array works, it just feels like I'm not doing the idiomatic thing.
I'm not using jQuery or another ajax library for this, so that's why I'm not using Bacon.fromPromise
You also may wonder why my consuming module wants the whole set instead of just the incremental update. If it were just appending rows that could be ok, but in my case it's an infinite scroll and it should draw data if both: 1. data is available and 2. area is on screen.
This can be done with the .scan() method. And also you will need a stream that emits items of one page, you can create it with .repeat().
Here is a draft code (sorry not tested):
var itemsPerPage = Bacon.repeat(function(index) {
var pageNumber = index + 1;
if (pageNumber < PAGE_LIMIT) {
return Bacon.fromCallback(function(callback) {
// your method that talks to the server
getDataForAPage(pageNumber, callback);
});
} else {
return false;
}
});
var allItems = itemsPerPage.scan([], function(allItems, itemsFromAPage) {
return allItems.concat(itemsFromAPage);
});
// Here you go
allItems.onValue(function(allUnicorns){
console.log("Got "+ allUnicorns.length +" Unicorns");
// ... some real display work
});
As you noticed, you also won't need .onValue(function(){}) hack, and curPage external state.
Here is a solution using flatMap and fold. When dealing with network you have to remember that the data can come back in a different order than you sent the requests - that's why the combination of fold and map.
var pages = Bacon.fromArray([1,2,3,4,5])
var requests = pages.flatMap(function(page) {
return doAjax(page)
.map(function(value) {
return {
page: page,
value: value
}
})
}).log("Data received")
var allData = requests.fold([], function(arr, data) {
return arr.concat([data])
}).map(function(arr) {
// I would normally write this as a oneliner
var sorted = _.sortBy(arr, "page")
var onlyValues = _.pluck(sorted, "value")
var inOneArray = _.flatten(onlyValues)
return inOneArray
})
allData.log("All data")
function doAjax(page) {
// This would actually be Bacon.fromPromise($.ajax...)
// Math random to simulate the fact that requests can return out
// of order
return Bacon.later(Math.random() * 3000, [
"Page"+page+"Item1",
"Page"+page+"Item2"])
}
http://jsbin.com/damevu/4/edit

Returning array from javascript class method to a script

I'm building a javascript application using object oriented techniques and I'm running into a problem that I hope someone here can help me resolve.
The following method is designed to return an array populated with rows of data from a web SQL database:
retrieveAllStoreSearches : function(){
this.db.transaction(
function(transaction){
transaction.executeSql(
"SELECT name,store,address FROM searchResults ORDER BY name ASC",
[],
function(transaction, results){
var returnArr = [];
for(var i = 0; i < results.rows.length; i++){
var row = results.rows.item(i);
returnArr.push(row.name + ' | ' + row.address);
}
console.log('Length of returnArr: ' + returnArr.length);
console.log(returnArr);
return returnArr;
},
this.errorHandler
);
}
);
}
This works exactly as expected when logging the results to the console BUT when I try to call the method in the following snippet (located in a different script - which initialises all objects and is responsible for building the application DOM structure and functionality)
console.log(db.retrieveAllStoreSearches());
undefined is returned.
I can't figure out what I am doing wrong as when I have used return in a method to allow an object to be accessed from one class and into a different script I have never encountered any problems.
Could anyone provide any pointers on what I might be doing wrong?
Cannot be done, if your function is calling an asynchronous function, the only way to return results is through a callback. That's the whole point of asynchronous functions, the rest of the code can keep going before the call is finished. It's a different way of thinking about returning values (without blocking the rest of your code).
So you'd have to change your code to the following (plus proper error handling)
retrieveAllStoreSearches : function(callback){
this.db.transaction(
function(transaction){
transaction.executeSql(
"SELECT name,store,address FROM searchResults ORDER BY name ASC",
[],
function(transaction, results){
var returnArr = [];
for(var i = 0; i < results.rows.length; i++){
var row = results.rows.item(i);
returnArr.push(row.name + ' | ' + row.address);
}
callback( returnArr );
},
this.errorHandler
);
}
);
}
Then you can use console.log like the following
db.retrieveAllStoreSearches(function(records) {
console.log(records )
});

Categories

Resources