Callback Hell!

Here is the structure of our application, which simulates getting transactions for the most recent loan associated with the account number of our user.

// getUser(), getLoans(), and getTransactions() not shown!

console.log("listening for events");
getUser(1, (user) => { 
  getLoans(user["Account number"], (loans) => {
    getTransactions(loans["Most recent"], (transactions) => {
      console.log(transactions);
    });
  });
});
console.log("still listening for events!");

Notice, if the functions getUser, getLoans, and getTransactions were synchronous, the program would look like this:

// getUser(), getLoans(), and getTransactions() not shown!

console.log("listening for events");
const user = getUser(1);
const loans = getLoans(user["Account number"]);    
const transactions = getTransactions(loans["Most recent"]);
console.log(transactions);
console.log("still listening for events!");

The second snippet corresponding to synchronous code is arguably more readable and easier to understand.

In the asynchronous version, because of the callbacks, we have a deeply nested structure. In practice, this can get much deeper. This pattern is referred to as the callback hell (some resources call it "Christmas tree problem").

method1(arg1, (arg2) => { 
  method2(arg2, (arg3) => {
    method3(arg3, (arg4) => { 
      method4(arg4, (arg5) => { 
        method5(arg5, (arg6) => {
          method6(arg6, (arg7) => {
            // ...
            // CALLBACK HELL
            // ... 
          }); 
        });   
      });   
    });
  });
});