In below code am I in callbackhell? How to overcome such scenario without using any async modules in pure javascript?
emailCallBack(e_data, email);
if (email_list.length) {
checkEmail(email_list.pop());
} else {
completionCallback();
}
The above code is copied in multiple location to make code work as expected.
function processInviteEmails(email_list, user_id, emailCallBack, completionCallback){
function checkEmail(email){
try {
check(email).isEmail();
//is valid email
checkConnected(email, user_id, function(connect_status, user_row, user_meta_row, connect_row){
var e_data;
//insert to connect and send msg to queue
if(connect_status === 'not connected'){
var cur_date = moment().format('YYYY-MM-DD');
var dbData = {
"first_name": '',
"last_name": '',
"email": email,
"user_id": user_id,
"status": "invited",
"unsubscribe_token": crypto.randomBytes(6).toString('base64'),
"created": cur_date,
"modified": cur_date
};
ConnectModel.insert(dbData, function(result){
if (result.insertId > 0) {
//send to email queue
//Queue Email
MailTemplateModel.getTemplateData('invitation', function(res_data){
if(res_data.status === 'success'){
var unsubscribe_hash = crypto.createHash("md5")
.update(dbData.unsubscribe_token + email)
.digest('hex');
var unsubscribe_link = app.locals.SITE_URL+'/unsubscribe/' + result.insertId + '/' + unsubscribe_hash;
var template_row = res_data.template_row;
var user_full_name = user_row.user_firstname+' '+ user_row.user_lastname;
var invitation_link = 'http://'+user_row.url_alias+'.'+ app.locals.SITE_DOMAIN;
var mailOptions = {
"type": 'invitation',
"to": dbData.email,
"from_name" : user_full_name,
"subject": template_row.message_subject
.replace('[[USER]]', user_full_name),
"text": template_row.message_text_body
.replace('[[USER]]', user_full_name)
.replace('[[INVITATION_LINK]]', invitation_link)
.replace('[[UNSUBSCRIBE_LINK]]', unsubscribe_link),
"html": template_row.message_body
.replace('[[USER]]', user_full_name)
.replace('[[INVITATION_LINK]]', invitation_link)
.replace('[[UNSUBSCRIBE_LINK]]', unsubscribe_link)
};
mailOptions = JSON.stringify(mailOptions);
//send email to queue
sqsHelper.addToQueue(cfg.sqs_invitation_url, mailOptions, function(data){
if(data){
e_data = null;
}
else{
e_data = new Error('Unable to Queue ');
}
emailCallBack(e_data, email);
if (email_list.length) {
checkEmail(email_list.pop());
} else {
completionCallback();
}
});
}
else{
e_data = new Error('Unable to get email template');
emailCallBack(e_data, email);
if (email_list.length) {
checkEmail(email_list.pop());
} else {
completionCallback();
}
}
});
}
else{
e_data = new Error('Unable to Insert connect');
emailCallBack(e_data, email);
if (email_list.length) {
checkEmail(email_list.pop());
} else {
completionCallback();
}
}
});
}
else{
e_data = new Error('Already connected');
emailCallBack(e_data, email);
if (email_list.length) {
checkEmail(email_list.pop());
} else {
completionCallback();
}
}
});
} catch (e) {
//invalid email
emailCallBack(e, email);
if (email_list.length) {
checkEmail(email_list.pop());
} else {
completionCallback();
}
}
}
checkEmail(email_list.pop());
}
"If you try to code bussiness db login using pure node.js, you go straight to callback hell"
I've recently created a simple abstraction named WaitFor to call async functions in sync mode (based on Fibers): https://github.com/luciotato/waitfor
check the database example:
Database example (pseudocode)
pure node.js (mild callback hell):
Note: The above code, although it looks like it will catch the exceptions, it will not. Catching exceptions with callback hell adds a lot of pain, and i'm not sure if you will have the 'res' parameter to respond to the user. If somebody like to fix this example... be my guest.
using wait.for:
Note: Exceptions will be catched as expected. db methods (db.select, db.execute) will be called with this=db
Your Code
In order to use wait.for, you'll have to STANDARDIZE YOUR CALLBACKS to function(err,data)
If you STANDARDIZE YOUR CALLBACKS, your code might look like:
see? no callback hell
I've put another solution in my blog. It is ugly but it is the most readable thing I could do with pure javascript.
Yes you are in callback hell. The solution assuming you don't want to use async (which I doubt you can justify other than prejudice) consists of:
1) Make more top-level functions. Each function should perform either 1 or 2 IO operations as a rule of thumb.
2) Call those functions, making your code follow a pattern of a long list of short core functions organized into business logic by a small list of control flow "glue" functions.
Instead of:
Aim for:
3) Use more function arguments instead of relying so much on closures
4) Emit events and DECOUPLE YOUR CODE! See how you have nested code writing stuff to the database and then building an email and adding it to a queue? Don't you see how those two do not need to exist one on top of the other? Emails lend themselves very well to a core business logic emitting events and an email module listening to those events and queueing the mail.
5) Decouple application-level service connection code from specific transaction business logic. Dealing with connections to network services should be handled more broadly and not embedded with a specific set of business logic.
6) Read other modules for examples
As to should you use an async library, you can and should make up your own mind about that but AFTER you know, and know pretty well, each and every one of these approaches:
Any serious node.js developer knows how to use and work within ALL of those paradigms. Yes, everyone has their favored approach and maybe some nerd rage about the non-favored approaches, but none of these are difficult and it's bad to get set in your decision without being able to point to some non-trivial code you wrote from scratch in each paradigm. Also, you should try several helper libraries and understand how they work and why they are going to save you boilerplate. Studying the work of Tim Caswell's
Step
or Caolan McMahon'sasync
is going to be very enlightening. Have you seen theeveryauth
source code's use of promises? I don't like it personally but I surely have to admit that the author has squeezed damn near every last bit of repetition out of that library, and the way he uses promises will turn your brain into a pretzel. These people are wizards with much to teach. Don't scoff at those libraries just for hipster points or whatever.Also a good external resource is callbackhell.com.