Call back hell means you are inside of a callback of inside another callback and it goes to nth call until your needs not fullfiled.
Let's understand through an example of fake ajax call by using set timeout API, lets assume we have a recipe API, we need to download all recipe.
<body>
<script>
function getRecipe(){
setTimeout(()=>{
const recipeId = [83938, 73838, 7638];
console.log(recipeId);
}, 1500);
}
getRecipe();
</script>
</body>
In the above example after 1.5 sec when timer expires inside code of call back will execute, in other words, through our fake ajax call all recipe will downloaded from the server. Now we need to download a particular recipe data.
<body>
<script>
function getRecipe(){
setTimeout(()=>{
const recipeId = [83938, 73838, 7638];
console.log(recipeId);
setTimeout(id=>{
const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
console.log(`${id}: ${recipe.title}`);
}, 1500, recipeId[2])
}, 1500);
}
getRecipe();
</script>
</body>
To download a particular recipe data we wrote code inside of our first callback and passed recipe Id.
Now let's say we need to download all the recipes of the same publisher of the recipe which id is 7638.
<body>
<script>
function getRecipe(){
setTimeout(()=>{
const recipeId = [83938, 73838, 7638];
console.log(recipeId);
setTimeout(id=>{
const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
console.log(`${id}: ${recipe.title}`);
setTimeout(publisher=>{
const recipe2 = {title:'Fresh Apple Pie', publisher:'Suru'};
console.log(recipe2);
}, 1500, recipe.publisher);
}, 1500, recipeId[2])
}, 1500);
}
getRecipe();
</script>
</body>
To full-fill our needs which is to download all the recipes of publisher name suru, we wrote code inside of our second call back. It is clear we wrote a callback chain which is called callback hell.
If you want to avoid callback hell, you can use Promise, which is js es6 feature, each promise takes a callback which is called when a promise is full-filled. promise callback has two options either it is resolved or reject. Suppose your API call is successful you can call resolve and pass data through the resolve, you can get this data by using then(). But if your API failed you can use reject, use catch to catch the error. Remember a promise always use then for resolve and catch for reject
Let's solve the previous callback hell problem using a promise.
<body>
<script>
const getIds = new Promise((resolve, reject)=>{
setTimeout(()=>{
const downloadSuccessfull = true;
const recipeId = [83938, 73838, 7638];
if(downloadSuccessfull){
resolve(recipeId);
}else{
reject('download failed 404');
}
}, 1500);
});
getIds.then(IDs=>{
console.log(IDs);
}).catch(error=>{
console.log(error);
});
</script>
</body>
Now download particular recipe:
<body>
<script>
const getIds = new Promise((resolve, reject)=>{
setTimeout(()=>{
const downloadSuccessfull = true;
const recipeId = [83938, 73838, 7638];
if(downloadSuccessfull){
resolve(recipeId);
}else{
reject('download failed 404');
}
}, 1500);
});
const getRecipe = recID => {
return new Promise((resolve, reject)=>{
setTimeout(id => {
const downloadSuccessfull = true;
if (downloadSuccessfull){
const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
resolve(`${id}: ${recipe.title}`);
}else{
reject(`${id}: recipe download failed 404`);
}
}, 1500, recID)
})
}
getIds.then(IDs=>{
console.log(IDs);
return getRecipe(IDs[2]);
}).
then(recipe =>{
console.log(recipe);
})
.catch(error=>{
console.log(error);
});
</script>
</body>
Now we can write another method call allRecipeOfAPublisher like getRecipe which will also return a promise, and we can write another then() to receive resolve promise for allRecipeOfAPublisher, I hope at this point you can do it by yourself.
So we learned how to construct and consumed promises, now let's make consuming a promise easier by using async/await which is introduced in es8.
<body>
<script>
const getIds = new Promise((resolve, reject)=>{
setTimeout(()=>{
const downloadSuccessfull = true;
const recipeId = [83938, 73838, 7638];
if(downloadSuccessfull){
resolve(recipeId);
}else{
reject('download failed 404');
}
}, 1500);
});
const getRecipe = recID => {
return new Promise((resolve, reject)=>{
setTimeout(id => {
const downloadSuccessfull = true;
if (downloadSuccessfull){
const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
resolve(`${id}: ${recipe.title}`);
}else{
reject(`${id}: recipe download failed 404`);
}
}, 1500, recID)
})
}
async function getRecipesAw(){
const IDs = await getIds;
console.log(IDs);
const recipe = await getRecipe(IDs[2]);
console.log(recipe);
}
getRecipesAw();
</script>
</body>
In the above example, we used an async function because it will run in the background, inside async function we used await keyword before each method which returns or is a promise because to wait on that position until that promise fulfilled, in other words in the bellow codes until getIds completed resolved or reject program will stop executing codes bellow that line when IDs returned then we again called getRecipe() function with a id and waited by using await keyword until data returned. So this is how finally we recovered from the callback hell.
async function getRecipesAw(){
const IDs = await getIds;
console.log(IDs);
const recipe = await getRecipe(IDs[2]);
console.log(recipe);
}
To use await we will need a async function, we can return a promise so use then for resolve promise and cath for reject promise
from the above example:
async function getRecipesAw(){
const IDs = await getIds;
const recipe = await getRecipe(IDs[2]);
return recipe;
}
getRecipesAw().then(result=>{
console.log(result);
}).catch(error=>{
console.log(error);
});