I am new to node and have been reading a ton lately on how best to avoid callback hell. Thanks to my novice abilities I have amassed a few pyramids in my code so I hit google to see how to avoid it.
I am building an app using the Salesforce Marketing Cloud API...
Here is my scenario.
So in old school code this is pretty straight forward, just do steps 1-4 in order.
In node I ended up with a function with a callback with a function with a callback.
So thanks to Google I came across the async library but am not quite sure how best to utilize it.
Here is a piece of my code, it's part of a larger module that I call from the main.js file that runs the server
MyHelper.prototype.test = function(name, req, callback) {
async.series([
function(next){
etHelper.folder_find("dataextension", name, next);
},function(next){
etHelper.folder_find("dataextension", "Data Extensions", next);
}
],
function(err, results){
console.log(results)
});
};
Should I be using the waterfall method of Async? If so how would I go about checking for the ID from step 1 and if it's not null exit
Not sure if it helps but here is the response that I have constructed from my ETHelper methods
[ { Success: false,
Status: 'Warning',
Message: 'Folder Not Found',
Type: 'dataextension',
Name: '10513542_SQLHelper_Test',
ID: null },
{ Success: true,
Status: 'OK',
Message: 'Folder Found',
Type: 'dataextension',
Name: 'Data Extensions',
ID: '1040721' } ]
I am really trying to learn here but am getting a little overwhelmed with the information that I have come across. It seems that there are 100 different ways to do stuff and it's really tough to know which one to choose.
Lastly, I don't want to muck up this post but here is the original method that I had come up with to handle this, it's a mess I know.
SQLHelper.prototype.checkInstall = function(name, req, callback) {
var response = new Object();
var deDone = false;
var qDone = false;
/*
*********************************************************************************************************
*
* This function will look for the SQLHelper folders. If they are not found they will be added
*
*********************************************************************************************************
*/
//Setup the DEFolder Lookup Filter
var deFolderParms = {
objectType: "DataFolder",
props: ["Name", "ID", "ContentType"],
filter: {
leftOperand: {
leftOperand: 'Name',
operator: 'equals',
rightOperand: name
},
operator: 'AND',
rightOperand:
{
leftOperand: 'ContentType',
operator: 'equals',
rightOperand: "dataextension"
}
}
};
//Look for the Folder
etHelper.getByName(deFolderParms, SoapClient, function(err, deFolderResponse){
//console.log(deFolderResponse);
if(deFolderResponse.length <=0)
{
//Folder Does not exist so create it
//But first we need the Parent Folder
var deParentFolderParms = {
objectType: "DataFolder",
props: ["Name", "ID", "ContentType"],
filter: {
leftOperand: {
leftOperand: 'Name',
operator: 'equals',
rightOperand: "Data Extensions"
},
operator: 'AND',
rightOperand:
{
leftOperand: 'ContentType',
operator: 'equals',
rightOperand: "dataextension"
}
}
};
etHelper.getByName(deParentFolderParms, SoapClient, function(err, deParentFolderResponse){
//console.log(deParentFolderResponse);
if(deParentFolderResponse.length > 0){
var newFolderParms = {
objectType: "DataFolder",
props: {
Name: name,
CustomerKey: name,
Description: "This folder is only to be used by the SQL Helper Application",
ContentType: "dataextension",
IsActive: true,
IsEditable: true,
AllowChildren: false,
ParentFolder:{
ID: deParentFolderResponse[0].ID
},
Client:{
ID: req.session.fuel.mid
}
},
options: {}
};
etHelper.create(newFolderParms, SoapClient, function(err, newFolderResponse){
//console.log(newFolderResponse);
if(newFolderResponse.length > 0){
deDone = true;
response.DEFolderID = newFolderResponse[0].NewID;
response.Status = true;
if(qDone)
{
callback(response);
}
}
else{
response.Status = false;
callback(response);
}
});
}
else
{
response.Status = false;
callback(response);
}
});
}
else
{
deDone = true;
response.DEFolderID = deFolderResponse[0].ID;
response.Status = true;
if(qDone)
{
callback(response);
}
}
});
//Setup the DEFolder Lookup Filter
var qFolderParms = {
objectType: "DataFolder",
props: ["Name", "ID", "ContentType"],
filter: {
leftOperand: {
leftOperand: 'Name',
operator: 'equals',
rightOperand: name
},
operator: 'AND',
rightOperand:
{
leftOperand: 'ContentType',
operator: 'equals',
rightOperand: "queryactivity"
}
}
};
//Look for the Folder
etHelper.getByName(qFolderParms, SoapClient, function(err, qFolderResponse){
//console.log(qFolderResponse);
if(qFolderResponse.length <=0)
{
//Folder Does not exist so create it
//But first we need the Parent Folder
var qParentFolderParms = {
objectType: "DataFolder",
props: ["Name", "ID", "ContentType"],
filter: {
leftOperand: {
leftOperand: 'Name',
operator: 'equals',
rightOperand: "Query"
},
operator: 'AND',
rightOperand:
{
leftOperand: 'ContentType',
operator: 'equals',
rightOperand: "queryactivity"
}
}
};
etHelper.getByName(qParentFolderParms, SoapClient, function(err, qParentFolderResponse){
//console.log(qParentFolderResponse);
if(qParentFolderResponse.length > 0){
var qnewFolderParms = {
objectType: "DataFolder",
props: {
Name: name,
CustomerKey: name,
Description: "This folder is only to be used by the SQL Helper Application",
ContentType: "queryactivity",
IsActive: true,
IsEditable: true,
AllowChildren: false,
ParentFolder:{
ID: qParentFolderResponse[0].ID
},
Client:{
ID: req.session.fuel.mid
}
},
options: {}
};
etHelper.create(qnewFolderParms, SoapClient, function(err, qnewFolderResponse){
//console.log(qnewFolderResponse);
if(qnewFolderResponse.length > 0){
qDone = true;
response.QueryFolderID = qnewFolderResponse[0].NewID;
response.Status = true;
if(deDone)
{
callback(response);
}
}
else{
response.Status = false;
callback(response);
}
});
}
else
{
response.Status = false;
callback(response);
}
});
}
else
{
qDone = true;
response.QueryFolderID = qFolderResponse[0].ID;
response.Status = true;
if(deDone)
{
callback(response);
}
}
});
};
Yes there are at least 100 different ways to solve this problem and many of them are not necessarily better than any other -- it depends on what you and your team are most comfortable with and what works best for your needs. You can really only figure that out with experience and the same solution will not be the best for you every time.
I suggest reading callbackhell.com, a manifesto on mitigating callback hell / pyramid of doom with continuation passing without an external library like async
.
In your case, using async.waterfall
would work. It is similar to async.series
except that it passes the non-error arguments to the next function in the series.
async.waterfall([
function(next){
etHelper.folder_find("dataextension", name, next);
},function(result, next){
if (null == result.ID) {
// stop everything
return next("some error");
}
etHelper.folder_find("dataextension", "Data Extensions", next);
}
], function (err, results) {
// handle `err` here if it's not null
});
Also be aware of Promises, which are another construct for handling synchronization. There are several node promise libraries out there like bluebird
.
Your above code would work in a similar fashion except that instead of folder_find
taking a callback argument it should return a promise that you reject internally in case of an error.
etHelper.folder_find("dataextension", name)
.then(function (result) {
if (null == result.ID) {
throw new Error("some error");
}
return etHelper.folder_find("dataextension", "Data Extensions");
})
.then(function (result) {
console.log(result);
})
.catch(function (err) {
// handle error here
});