Promises and Callback hells are not about indentation

I had to work with nodejs a lot lately, and the project required the use of lots of rest services. I got a lot of asynchronous pain.

At first, I didn’t understand promises well, so I didn’t use them. Things went so bad until I made some research and understood how to work with lots of asynchronous code without losing -all- my sanity.

Callback style asynchronous code have a lot of problems, being the most well known an obvious the fact that indentation can easily become BIG if you have to chain several asynchronous pieces of code. Let’s imagine we have a prompt() function that asynchronously shows a messages to the user asking for some text in a modal input, and then pass that text to a callback function as value. We could make a system on top of it to ask an user for her name, surname and age:

prompt.get('enter your name' , function(err, value ){
var name = value;
prompt.get('enter your surname' , function(err , value){
var surname = value;
promt.get('enter your date' , function(err , value ){
doSomething(name , surname , value);
});
});
});

As is seen, every subsequent callback in the code is more and more indented to the right, and if that piece of code would ask for seven or eight fields rather than three, would be very difficult to know where exactly does any new callback function start and end. Also, in real code where not all the callbacks have the exactly same structure, to read the code properly would be really difficult.

Abroad the internet I saw a lot of people arguing that the problem with the callback style code can be solved by naming and properly refactoring the anonymous callback functions so that this indentation hell disappears. An example of a more sane code using such a technique could be:

var getName = function(surnameCallback , ageCallback ){
    prompt.get('enter the name' , function(err , value){
data = {};
data.name = value;
surnameCallback(ageCallback , data);
});
}
var getSurname = function(cb , data){
    prompt.get('enter the surname' , function(err , value ){
data.surname = value;
cb(data);
});
}
vat getAge = function(data ){
    prompt.get('enter the date' , function(err , value){
doSomething( data.name , data.surname , value);
});
};
//now all the code is used by the next line: no indentation hell at //all
getName( getSurname , getAge );

Now the indentation exists just in every small function, so it doesn’t stack. Even if we would have a chain of seven prompt functions, there will not be extra indentation after every function call, but a top level function call like:

getName(getSurname, getNationality, getAge, getSomething... );

However, this solves only a problem about reading the code and indentation. Now to read and understand the code is really easier. However, is still a mess to reuse or modify it, which is pretty important for a piece of code.

In that version of the code, getName is coded in a specific way so the whole system can work to get just the name, the surname and the age of the user. There is the illusion that getSurname, for instance, is a valid reusable piece of code, and that another systems asking for more or less fields could use it with ease. Actually that’s not the case, as we will see, and that’s the main problem of the callback style.

Ok, imagine that you need to modify the system and get the zip code of the user between the surname and the age. The first step could be set up a function like:

var getZipCode = function(cb , data){
prompt.get('enter the zip code' , function(err , value ){
data.zipCode = value;
cb(data);
});
}

Which seems fairly useful and stand-alone.

The second step, however, should be some getName() refactor to get getZipCode() to work with the rest of the functions. For instance something like:

var getName = function(surnameCallback , zipCodeCallback, ageCallback ){
prompt.get('enter the name' , function(err , value){
data.name = value;
surnameCallback(zipCodeCallback , ageCallback , data);
} );
}

But in order to use zipCodeCallback, we need surnameCallback to accept two callbacks now, or we don’t have any way to place zipCodeCallback in the chain and make it do its work. Now getSurname could be something like:

var getSurname = function(zipCodeCallback, ageCallback , data){
prompt.get('enter the surname' , function(err , value ){
data.surname = value;
zipCodeCallback( ageCallback , data);
});
}

We needed to modify the code of two functions in order to add another field to the system. This may even not be doable at all if there is other code actually calling those functions, and we must then duplicate new functions with the new added behaviour. To add and remove fields to the system leads to a lot of problems.

Basically the problem here is not indentation at all. Promises solve that problem too, but that is just an eventual advantage. The point of the promises is that they allow to use asynchronous functions as stand-alone pieces of code, that can be used, chained and tested in isolation from the others.

In fact, there is another problem we didn’t talked about: the doSomething function is hard coded into getAge(). That’s bad, but to avoid it we would need to pass another callback function as a parameter through all the chain and invoke that callback at the end instead of the hard-coded doSomething() function. Actually, we must pass a callback to getName for every operation we want to do subsequently.

That, the continuous passing of subsequent operations is the core of the callback hell. Whatever operation you need to do with the result of a series of asynchronous calls, you can’t just call it with the result as a parameter: you must pass the operation itself to the whole system, which implies to duplicate or modify some code.

Instead, let’s see what happens if we have a promptAsync function that is the result of the promisification of prompt(), thus returning a promise instead of firing a callback. Then, our code would be like:

promptAsync('your name')
.then(function(val){
data.name = val;
return promptAsync('your surname');
})
.catch(console.log)
.then(function(val){
data.surname = val;
return return promptAsync('your age');
})
.catch(console.log)
.then(function(val){
data.age = val;
})
.catch(console.log)
.then(anyFunctionThatUsesData);

So, if we want to add the zipCode, we don’t need to modify any of the code that get the name, the surname or the age, we just pipe another promptAsync() call in a promise:

promptAsync('your name')
.then(function(val){
data.name = val;
return promptAsync('your surname');
})
.catch(console.log)
.then(function(val){
data.surname = val;
return return promptAsync('your age');
})
.catch(console.log)
.then(function(val){
data.age = val;
})
.catch(console.log)
.then(function(){
/*Here we add zip code to the chain*/
return promptAsync('your zip code');
})
.catch(console.log)
.then(function(val){
data.zipCode = val;
})
.then(anyFunctionThatUsesData);

That can be bad if the last function we’re calling take data as a parameter instead of read from a global; but that’s easy to fix by just piping more code and pass a promise with data to the last function:

...
.then(function(val){
data.zipCode = val;
})
.then(function(){
return data;
})
.then(anyFunctionThatTakesDataAsParameter);

Sure we are modifying the code to make those changes. Actually, when you add functionality to a system, some code must be modified or write from scratch. However, we are not messing with the very bricks of our system, like modifying getSurname() to pass an extra callback. Should be name, surname and age a tuple relevant to our system and useful to various parts of the system, we could just write down a function like:

var getUserData = function(){
var data = {};
return promptAsync('your name')
.catch(console.log)
.then(function(val){
data.name = val;
return promptAsync('your surname');
})
.catch(console.log)
.then(function(val){
data.surname = val;
return promptAsync('your age');
})
.catch(console.log)
.then(function(val){
data.age = val;
return data;
});
}

And we would be able to reuse it again and again with any operation like:

getUserData.then(console.log);
getUserData.then(writeToDatabase);
getUserData.then(sendDataToRestService); 

Without messing with passing callbacks to the end of the callback chain, and without duplicating code. We need a super version of the function to get the zip code too and we want it to be reusable? Just:

var getCompleteUserData = function(){

var data = {};
    return getUserData()
.catch(console.log)
.then(function(result){
data = result;
return promptAsync('your zip code');
})
.catch(console.log)
.then(function(value){
data.zipCode = value;
return data;
});
}

and getCompleteUserData() gets the zipCode without messing with the getUserData() code and is still reusable.

So, whenever you think you can clean up an asynchronous callback hell just with some refactoring into smaller functions, keep in mind that functions won’t be reusable and that any other operation you want to do with the result of your calls will need to mess with the code itself and to pass extra callbacks. If your system just needs to get an auth token from the file system asynchronously and then connect to a REST service, maybe you can just invoke a callback inside another, if you’re so sure you don’t need to do anything with the result of the remote call. But if you need to chain several number of remote services calls, and/or use those services as basic components of your system, to use plain asynchronous callback style code will for sure deplete your sanity faster than dancing with Ktulu.