What are the callbacks, callback hell, and inversion of control (IOC) in JavaScript in depth

What are the callbacks, callback hell, and inversion of control (IOC) in JavaScript in depth

What are the call-back functions?

In JavaScript, a callback function is a function that is passed as an argument to another function and is executed after the completion of some operation. Callbacks are commonly used in asynchronous programming, where functions don't necessarily execute linearly.

Let's see examples to understand callbacks.

Synchronous Callback:

// Function that takes a callback
function greet(name, callback) {
    console.log("Hello, " + name + "!");
    callback(); // Execute the callback function
}

// Callback function
function sayGoodbye() {
    console.log("Goodbye!");
}

// Using the greet function with a callback
greet("Muhammad Bilal", sayGoodbye);
Output: 
Hello, Muhammad Bilal!
Goodbye!

Asynchronous Callback

// Example function with a callback
function doSomething(callback) {
  console.log("Doing something...");
  // Simulating an asynchronous operation with setTimeout
  setTimeout(function () {
    console.log("Operation completed!");
    // Execute the callback function
    callback();
  }, 2000); // 2000 milliseconds (2 seconds) delay
}

// Define a callback function
function afterSomething() {
  console.log("After the operation!");
}

// Call the main function with the callback
doSomething(afterSomething);
OUtput:
Doing something...
Operation completed!
After the operation!

Callbacks with Parameters

// Function with a callback and parameters
function multiplyAsync(a, b, callback) {
  setTimeout(function () {
    const result = a * b;
    callback(result);
  }, 1000); // 1000 milliseconds (1 second) delay
}

// Callback function with parameter
function handleResult(result) {
  console.log("Result is:", result);
}

// Call the main function with the callback
multiplyAsync(5, 10, handleResult);
Output: Result is: 50

Using Anonymous Functions

// Callback with an anonymous function
function greet(name, callback) {
  setTimeout(function () {
    const greeting = "Hello, " + name + "!";
    callback(greeting);
  }, 1500); // 1500 milliseconds (1.5 seconds) delay
}

// Call the main function with an anonymous callback function
greet("Muhammad Bilal", function (message) {
  console.log(message);
});
Output: Hello, Muhammad Bilal!

They are commonly used in scenarios like event handling, AJAX requests, and other asynchronous operations where you want to execute code after a certain task is completed.

Why don't you use callbacks?

1. Callback Hell (Pyramid Of DOOM)

Callback hell, also known as the "pyramid of doom," is a situation in JavaScript programming where multiple nested callbacks create complex and hard-to-read code. This often occurs when dealing with asynchronous operations, such as handling callbacks for events, AJAX requests, or other asynchronous tasks.

For example:

function fetchData(data, callback) {
// Asynchronous operation
  setTimeout(() => {
    console.log(data);
    if (callback) {
      callback(data);
    }
  }, 1000);
};

// Callback hell
fetchData('Data Fetched', ()=>{
  fetchData('Data Processed', ()=>{
    fetchData('Data Saved', ()=>{
      fetchData('Data Forward', ()=>{
        fetchData('Data Deleted', ()=>{
          fetchData('All operations are completed !');
        });
      });
    });
  });
});

Output: // Ptint data after one one seconds of delay
Data Fetched
Data Processed
Data Saved
Data Forward
Data Deleted
All operations are completed !

In the above example, every callback function should be executed after one second. If you want to call the callback functions at different times, then you have to write the code like this:

function fetchData(callback1) {
    // Asynchronous operation
    setTimeout(function () {
        console.log("Data fetched");
        callback1();
    }, 1000);
};

function processData(callback2) {
    // Asynchronous operation
    setTimeout(function () {
        console.log("Data processed");
        callback2();
    }, 2000);
};

function saveData(callback3) {
    // Asynchronous operation
    setTimeout(function () {
        console.log("Data saved");
        callback3();
    }, 1000);
};
function forwardData(callback4) {
    // Asynchronous operation
    setTimeout(function () {
        console.log("Data forward");
        callback4();
    }, 1500);
};
function deleteData(callback5) {
    // Asynchronous operation
    setTimeout(function () {
        console.log("Data deleted");
        callback5();
    }, 1000);
};
function operationCompleted() {
    // Asynchronous operation
    setTimeout(function () {
        console.log("All operations are completed !");
    }, 2000);
};

// Callback hell
fetchData( ()=> {
    processData( ()=> {
        saveData( ()=> {
          forwardData( ()=>{
            deleteData(operationCompleted);
          });
        });
    });
});

Output: // Ptint data after given seconds of delay
Data fetched // Print after 1s
Data processed // Print after 2s
Data saved // Print after 1s
Data forward // Print after 1.5s
Data deleted // Print after 1s
All operations are completed ! // Print after 2s

2. Inversion Of Control

Inversion of Control (IoC) is a design principle where the control flow of a program is inverted, meaning control is transferred from the main program to external components. In the context of callbacks in JavaScript, IoC is often implemented using functions that are passed as arguments to other functions, allowing the calling function to control the execution of the passed function. In simple words, we lose control of our code while using callbacks. Because it is very risky, we gave control of our program to another function.

For Example:

// Function that takes a callback
function fetchData(url, callback) {
  // Simulating an asynchronous operation 
(e.g., fetching data from a server)
  setTimeout(() => {
    const data = { message: 'Data fetched successfully!' };
    // Calling the provided callback with the fetched data
    callback(data);
  }, 2000);
}

// Callback function to handle the fetched data
function handleData(data) {
  console.log(data.message);
}

// Using the fetchData function with IoC
fetchData('https://example.com/api/data', handleData);

In this example, suppose we are fetching our data through a URL that is an asynchronous call. We passed it as a call back in a function, and now we are relaxed and depend on that function. Now we don't know when it will get information or whether it will get it or not. We have no control over it. This is the inversion of control (IOC).

Now we will move on to promises in JavaScript in the next blog.