"Better" is hard to define when it comes to programming style so its hard to give a "best" answer to your question without knowing more about what you are doing and how your code is structured.
That said, one good rule of thumb is trying to keep your code organizing in terms of how you would describe your problem as opposed to writing code that only cares about control flow. Its not a hard rule though, on one end you want to avoid huge functions that do everything but on the other hand you want to avoid functions that are to small and jump around too much calling other functions or callbacks.
First of all, lets pretend that your code is only synchronous. Most languages are like this and its a shame that Javascript forces callbacks down on you. One thing you can do is create functions that receive parameters and return appropriate values. For example:
//This puts the "is user admin" logic in a single place
//Also note how we can give a good name for the function. If you have a
//hard time naming the function its a hint that maybe you shouldn't be creating
//the function in the first place.
function isAdmin(userData){
return userData.id === 1;
}
If its just a few lines of code that are used once, you can just leave them inline
// Names like "process X" or "do X" are not very descriptive.
//See what I mean by it would be good to know extra context?
function processUser(userData){
// Your AJAX code should handle the JSON.Parse.
// I prefer having the code that does the work receive the JS objects
// because that makes things easier to test and to integrate with other JS code
// (no need to convert things to string just to pass to this function)
if(isAdmin(userData)){
//do
//some stuff
return someValue;
}else{
//do some other stuff
return someOtherValue;
}
}
Note that I added explicit "returns" here. Even if your code isn't actually returning anything, this will come handy for understanding the transition to callbacks.
If your code gets too big you can move it to some other function:
function processAdmin(user){
// dozens of lines of code...
return someValue;
}
function processUnpriviledgedUser(user){
//...
return someOtherValue;
}
function processUser(userData){
if(isAdmin(userData)){
return processAdminUser(userData);
}else{
return processUnpriviledgedUser(userData);
}
}
function fetchUser(){
//lets pretend that AJAX is synchronous for a moment...
var rawData = $.magicAjax("www.example.com");
var user = JSON.Parse(data);
var val = proccessUser(user);
return val;
}
Note that one thing we get for free with function is that we can call them from multiple places:
var result = proccessUnpriviledgedUser({id:2, name:"hugomg"});
console.log( result );
Now, lets get to callbacks. If all your "processUser" stuff is synchronous, you can probably refactor it using normal functions like I explained before. Unfortunately, if you want to do any AJAX call or otehr async stuff inside, you can't rely on return statements like before. Instead, you need to replace them with an onDone callback that receives as a parameter the stuff you would have returned.
For example, our processUser might look like
function processUser(userData, onDone){
if(isAdmin(userData)){ // <-- we can still use sync code when not doing IO
$.ajax("bla", {success: function(){
//blaz;
onDone(someValue);
});
}else{
//
}
}
function fetchUser(onDone){
$.ajax("www.example.com", {
success: function(data){
var user = JSON.Parse(data);
proccessUser(function(val){
onDone(val);
});
}
});
}
function main(){
fetchUser(function(val){
console.log("Fetched the user", val);
});
}
Note how we can use the same rules we used to for splitting code in separate functions - the same idea about code size and names still applies. The only difference is that functions doing async stuff need to use callbacks instead of return. In particular, note how we added onDone callbacks to fetchUser and proccessUser. Not only did we create our own async functions, but the callback parameters mean that we don't need to hardcode what gets called after the function ends - just like return goes back to whatever caller function that called us, onDone callbacks means we jump to whatever callback the caller wants us to (instead of always jumping to the same place once we are done).
proccessUnpriviledgedUser({id:2, name:"hugomg"}, function(result){
console.log(result);
});
Finally, I would like to point out that a big disadvantage of async code is the error handling. In order to handle "try-catch" across async functions similarly to how its handled across sync functions you might also need to pass around onError callbacks in addition to the onDone callbacks. Because this is very boilerplaty and very easy to get right, I would recommend using some sort of library to help manage your async code (either a promise based or cb based one should be OK) if you ever start writing non trivial async code.