Muhammad Ovi - Blog
HomeAbout MeContact

Callback hell or try catch hell

By Muhammad Ovi
Published in JavaScript
June 19, 2021
2 min read
Callback hell or try catch hell

What are “Callbacks”?

A callback function is usually used as a parameter to another function.

The function that receives callback function is normally fetching data from a database, making an API request, downloading a file, which usually takes a while.

Assume getting some data from the API and the request takes around 2 seconds to complete.
Now, you can either wait for the API call to complete and then display your UI,

OR, you show everything else and show a loader where the API data needs to be shown.

In the API function, we pass some sort of “call back” function that replaces loader with actual data, so once the response is received from API, it calls the callback function with the data and, then our callback function replaces the loader. Let’s see this in action:

function getDataFromAPI(callbackFunction) {
  fetchSomeData().then((data) => {
    callbackFunction(data);
  });
}

getDataFromAPI(function replaceLoaderWithData(data) {
  // your awesome logic to replace loader with data
});

OR

// from w3schools
function myDisplayer(sum) {
  document.getElementById('demo').innerHTML = sum;
}

function myCalculator(num1, num2, myCallback) {
  let sum = num1 + num2;
  myCallback(sum);
}

myCalculator(5, 5, myDisplayer);

Okay, you already know this. We’re not learning what callbacks are.

What is “callback hell”?

If your application logic is not too complex, a few callbacks seem harmless. But once your project requirements start to increase, you will quickly find yourself piling layers of nested callbacks. Like this:

getAreas(function (areas) {
  getTowns(function (towns) {
    getCities(function (cities) {
      getCountries(function (countries) {
        getContinents(function (continents) {
          getPlanets(function (planets) {
            getSolarSystems(function (solarSystems) {
              getGalaxies(function (galaxies) {
                // Welcome to the callback hell...
              });
            });
          });
        });
      });
    });
  });
});

Of course, we can use JavaScript’s Promise and move to .then & .catch.

getAreas().then(function (areas) {
  getTowns().then(function (towns) {
    getCities().then(function (cities) {
      getCountries().then(function (countries) {
        getContinents().then(function (continents) {
          getPlanets().then(function (planets) {
            getSolarSystems().then(function (solarSystems) {
              getGalaxies().then(function (galaxies) {
                // Welcome to the callback hell AGAIN...
              });
            });
          });
        });
      });
    });
  });
});

Congrats! Welcome to Callback Hell.

Callback Hell, also known as Pyramid of Doom, is a slang term used to describe an unwieldy number of nested “if” statements or functions.

Async Await to the rescue!

Async await feels like heaven because it avoids the callback hell or pyramid of doom by writing asynchronous code in a clean line-by-line format.

The above code changes to this:

// assuming the environment supports direct async function
const areas = await getAreas();
const towns = await getTowns();
const cities = await getCities();
const countries = await getCountries();
const continents = await getContinents();
const planets = await getPlanets();
const solarSystems = await getSolarSystems();
const galaxies = await getGalaxies();

😳😲😳
// now this... looks awesome!!!

BUT…

This is awesome until error handling comes into play because you end up with the try-catch tower of terror!

All your beautiful one-liners magically expand to at least five lines of code…

// assuming the environment supports direct async function

try {
  const areas = await getAreas();
} catch (err) {
  // handleError(err)
}

try {
  const towns = await getTowns();
} catch (err) {
  // handleError(err)
}

try {
  const cities = await getCities();
} catch (err) {
  // handleError(err)
}

try {
  const countries = await getCountries();
} catch (err) {
  // handleError(err)
}

// ... and so on.

You can find yourself an easy way which is simply by appending the catch method to the end of each promise.

// assuming the environment supports direct async function
const areas = await getAreas().catch((err) => handleError(err));
const towns = await getTowns().catch((err) => handleError(err));
const cities = await getCities().catch((err) => handleError(err));
const countries = await getCountries().catch((err) => handleError(err));
const continents = await getContinents().catch((err) => handleError(err));
const planets = await getPlanets().catch((err) => handleError(err));
const solarSystems = await getSolarSystems().catch((err) => handleError(err));
const galaxies = await getGalaxies().catch((err) => handleError(err));

This looks better, but! This is still getting repetitive.

Another better option is to create a standardized error handling function.

The function would first resolve the promise then returns an array.
In that array, the first element is the data and the second element is an error.

If there’s an error then the data is null and the error is defined, like this:

async function promiseResolver(promise) {
  try {
    const data = await promise();
    return [data, null];
  } catch (err) {
    return [null, err];
  }
}

Now when you call this function in your code you can destructure it to get a clean one-liner with error handling or use a regular if statement if you want to do something else with the error.

Your main function would look something like this:

// assuming the environment supports direct async function
const [areas, areasErr] = await promiseResolver(getAreas);
const [towns, townsErr] = await promiseResolver(getTowns);
const [cities, citiesErr] = await promiseResolver(getCities);

if (citiesErr) {
  // do something
}

const [countries, countriesErr] = await promiseResolver(getCountries);
const [continents, continentsErr] = await promiseResolver(getContinents);
const [planets, planetsErr] = await promiseResolver(getPlanets);
const [solarSystems, solarSystemsErr] = await promiseResolver(getSolarSystems);
const [galaxies, galaxiesErr] = await promiseResolver(getGalaxies);

if (galaxiesErr) {
  // do something
}

// ... and so on.

That’s all folks! Hope you found this helpful, see you in the next one 😉


Tags

javascriptnode
Previous Article
Some HTML Attributes You May Not Be Using
Muhammad Ovi

Muhammad Ovi

Sr. Software Engineer

Topics

JavaScript
NodeJS
Other

Related Posts

20 JavaScript One-Liners That Will Help You Code Like a Pro
May 09, 2021
3 min

Promotion

NewTabTodo
Add todos and manage your day to day task within your browser!
Get it now
© 2021, All Rights Reserved.

Quick Links

AboutContact

Social Media