What is "callback hell" and how and why RX solves it?

Can someone give a clear definition together with a simple example that explains what is a "callback hell" for someone who does not know JavaScript and node.js ?

When (in what kind of settings) does the "callback hell problem" occur?

Why does it occur?

Is "callback hell" always related to asynchronous computations?

Or can "callback hell" occur also in a single threaded application?

I took the Reactive Course at Coursera and Erik Meijer said in one of his lectures that RX solves the problem of "callback hell". I asked what is a "callback hell" on the Coursera forum but I got no clear answer.

After explaining "callback hell" on a simple example, could you also show how RX solves the "callback hell problem" on that simple example?


1) What is a "callback hell" for someone who does not know javascript and node.js ?

This other question has some examples of Javascript callback hell: How to avoid long nesting of asynchronous functions in Node.js

The problem in Javascript is that the only way to "freeze" a computation and have the "rest of it" execute latter (asynchronously) is to put "the rest of it" inside a callback.

For example, say I want to run code that looks like this:

x = getData();
y = getMoreData(x);
z = getMoreData(y);
...

What happens if now I want to make the getData functions asynchronous, meaning that I get a chance to run some other code while I am waiting for them to return their values? In Javascript, the only way would be to recode everything that touches an async computation using continuation passing style:

getData(function(x){
    getMoreData(x, function(y){
        getMoreData(y, function(z){ 
            ...
        });
    });
});

I don't think I need to convince anyone that this version is uglier than the previous one. :-)

2) When (in what kind of settings) does the "callback hell problem" occur?

When you have lots of callback functions in your code! It gets harder to work with them the more of them you have in your code and it gets particularly bad when you need to do loops, try-catch blocks and things like that.

For example, as far as I know, in JavaScript the only way to execute a series of asynchronous functions where one is run after the previous returns is using a recursive function. You cant use a for loop.

//we would like to write
for(var i=0; i<10; i++){
    doSomething(i);
}
blah();

Instead, we might need to end up writing:

function loop(i, onDone){
    if(i >= 10){
        onDone()
    }else{
        doSomething(i, function(){
            loop(i+1, onDone);
        });
     }
}
loop(0, function(){
    blah();
});

//ugh!

The number of questions the JavaScript tag gets asking how to do this should be a testament to how confusing it is :)

3) Why does it occur ?

It occurs because in JavaScript the only way to delay a computation so that it runs after the asynchronous call returns is to put the delayed code inside a callback function. You cannot delay code that was written in traditional synchronous style so you end up with nested callbacks everywhere.

4) Or can "callback hell" occur also in a single threaded application?

Asynchronous programming has to do with concurrency while a single-thread has to do with parallelism. The two concepts are actually not the same thing.

You can still have concurrent code in a single threaded context. In fact, JavaScript, the queen of callback hell, is single threaded.

Concurrency vs Parallelism - What is the difference?

5) could you please also show how RX solves the "callback hell problem" on that simple example.

I don't know anything about RX in particular, but usually this problem gets solved by adding native support for asynchronous computation in the programming language. This can very a lot and can come in different names (async, generators, coroutines, callcc, ...). For a crappy concrete example, in Python we can use generators to to code that loop example with something along the lines of:

def myLoop():
    for i in range(10):
        doSomething(i)
        yield

myGen = myLoop()

This is not the full code but the idea is that the "yield" pauses our for loop until someone calls myGen.next(). The important thing is that we could still write the code using a for loop, without needing to turn out logic "inside out" like we had to do in that recursive loop function.


Just answer the question: could you please also show how RX solves the "callback hell problem" on that simple example?

The magic is flatMap . We can write the following code in Rx for @hugomg's example:

def getData() = Observable[X]
getData().flatMap(x -> Observable[Y])
         .flatMap(y -> Observable[Z])
         .map(z -> ...)...

It's like you are writing some synchronous FP codes, but actually you can make them asynchronous by Scheduler .


Callback hell is any code where the use of function callbacks in async code becomes obscure or difficult to follow. Generally, when there is more than one level of indirection, code using callbacks can become harder to follow, harder to refactor, and harder to test. A code smell is multiple levels of indentation due to passing multiple layers of function literals.

This often happens when behaviour has dependencies, ie when A must happen before B must happen before C. Then you get code like this:

a({
    parameter : someParameter,
    callback : function() {
        b({
             parameter : someOtherParameter,
             callback : function({
                 c(yetAnotherParameter)
        })
    }
});

If you have lots of behavioural dependencies in your code like this, it can get troublesome fast. Especially if it branches...

a({
    parameter : someParameter,
    callback : function(status) {
        if (status == states.SUCCESS) {
          b(function(status) {
              if (status == states.SUCCESS) {
                 c(function(status){
                     if (status == states.SUCCESS) {
                         // Not an exaggeration. I have seen
                         // code that looks like this regularly.
                     }
                 });
              }
          });
        } elseif (status == states.PENDING {
          ...
        }
    }
});

This won't do. How can we make asynchronous code execute in a determined order without having to pass all these callbacks around?

RX is short for 'reactive extensions'. I haven't used it, but Googling suggests it's an event-based framework, which makes sense. Events are a common pattern to make code execute in order without creating brittle coupling . You can make C listen to the event 'bFinished' which only happens after B is called listening to 'aFinished'. You can then easily add extra steps or extend this kind of behaviour, and can easily test that your code executes in order by merely broadcasting events in your test case.

链接地址: http://www.djcxy.com/p/13920.html

上一篇: java中的并发和并行有区别吗?

下一篇: 什么是“回拨地狱”以及RX如何以及为何解决它?