I'm tired to the utmost degree of all these posts about people (supposedly) coming from PHP/C#/Ruby/Python background and seeing "absolutely no problems" with JS syntax, object model and programming paradigms. There are problems. They are objectively there. If you don't see them, you have to check your critical thinking skills, rather than imply that everyone outside of elite JS circles are simply too ignorant to understand its awesomeness.
The simplest example of callback hell is trying to analyze workflow of some chunk of code in a debugger. If the code is linear, you place a breakpoint at the beginning of the method you're interested in and go through the code one line at a time. If there are nested statement of method calls, the debugger happily redirect you to them without fail.
With extensive use of callbacks, this becomes impossible. Since callbacks are merely registered in the original method, you need to place a breakpoint at the beginning of every callback function you might encounter in advance. Named callbacks actually make this worse by physically separating the place where a function is registered from its body. Did I mention that you're loosing ability to do any kinds of static reasoning, since callbacks are inherently a runtime concept? And the fact that you loose ability to look at the stack trace to "reverse engineer" why something was called?
Which reminds me of something. Have you ever seen code that reads a global variable, and you have no clue where the value came from? Callbacks create the exact same problem, except they aren't just data, they are code, so the problem can be nested multiple times.
IMO, this is more of a problem of indirections than its a problem of callbacks.
If you use anonymous function as the callback there is no indirection and you know perfctly well where to set the break point.
At the same time, you can also have the sort of debugging problem you mentioned in regular code whenever you call a method in some polymorphic object. (the "listener" pattern is just one example of this)
Well in addition to not having a problem with callback hell I also haven't used a debugger in more than three years, so I guess I'm just weird.
As I said I like to write unit tests with my named callbacks. This allows me to test the callbacks as well as the root level functions that make use of these callbacks to ensure that everything is working perfectly.
When I follow this model it is extremely rare that I ever encounter any issues that would need a debugger, and if a problem does arise somewhere the relevant unit test can quickly expose which callback is having a trouble, and precisely what is wrong.
I'm not saying callbacks are perfect. My goal is just to share my technique for organizing my code in Node which I feel has led to some very well organized, testable, and maintainable code.
When someone gives you a sufficiently large codebase written by other people and asks why when they click A they get B, you have two options:
1. Read the code and try to reason about it.
2. Fire up the debugger and replicate user actions.
Guess what? Callbacks in JS make option #1 significantly harder, since they are, essentially, runtime weakly typed mechanism for code composition.
The simplest example of callback hell is trying to analyze workflow of some chunk of code in a debugger. If the code is linear, you place a breakpoint at the beginning of the method you're interested in and go through the code one line at a time. If there are nested statement of method calls, the debugger happily redirect you to them without fail.
With extensive use of callbacks, this becomes impossible. Since callbacks are merely registered in the original method, you need to place a breakpoint at the beginning of every callback function you might encounter in advance. Named callbacks actually make this worse by physically separating the place where a function is registered from its body. Did I mention that you're loosing ability to do any kinds of static reasoning, since callbacks are inherently a runtime concept? And the fact that you loose ability to look at the stack trace to "reverse engineer" why something was called?
Which reminds me of something. Have you ever seen code that reads a global variable, and you have no clue where the value came from? Callbacks create the exact same problem, except they aren't just data, they are code, so the problem can be nested multiple times.