什么是“回调地狱”,RX如何以及为什么解决它?

有人能给出一个明确的定义和一个简单的例子,解释一个不了解JavaScript和node的人的“回调地狱”是什么.js?

何时(在何种设置下)发生“回调地狱问题”?

为什么会发生这种情况?

“回调地狱”总是与异步计算有关吗?

或者“回调地狱”也可以发生在单线程应用程序中吗?

我在Coursera参加了反应式课程,Erik Meijer在他的一次讲座中说RX解决了“回调地狱”的问题。我在Coursera论坛上问什么是“回调地狱”,但我没有得到明确的答案。

在一个简单的例子中解释了“回调地狱”之后,你能否也在这个简单的例子中展示RX如何解决“回调地狱问题”?


答案 1

1)对于不了解javascript和node的人来说,什么是“回调地狱.js?

另一个问题有一些Javascript回调地狱的例子:如何避免在Node中异步函数的长嵌套.js

Javascript中的问题在于,“冻结”计算并让“其余部分”在后面(异步)执行的唯一方法是将“其余部分”放在回调中。

例如,假设我想运行如下所示的代码:

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

如果现在我想使 getData 函数异步,这意味着我有机会在等待它们返回其值时运行其他一些代码,会发生什么情况?在Javascript中,唯一的方法是使用继续传递样式重写与异步计算相关的所有内容:

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

我不认为我需要说服任何人这个版本比前一个版本更丑陋。:-)

2)何时(在什么样的设置下)发生“回调地狱问题”?

当您的代码中有很多回调函数时!在代码中使用它们越多,使用它们就越困难,当您需要执行循环,尝试捕获块之类的事情时,情况会变得特别糟糕。

例如,据我所知,在JavaScript中,执行一系列异步函数的唯一方法是使用递归函数,其中一个函数在前面的返回之后运行。不能使用 for 循环。

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

相反,我们可能需要最终写:

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

//ugh!

我们在StackOverflow上遇到的许多问题询问如何做这种事情,这证明了它是多么令人困惑:)

3)为什么会发生这种情况?

发生这种情况是因为在JavaScript中,延迟计算以便在异步调用返回后运行的唯一方法是将延迟的代码放在回调函数中。您不能延迟以传统同步样式编写的代码,因此最终会在任何地方都嵌套回调。

4)或者“回调地狱”也可以发生在单线程应用程序中吗?

异步编程与并发性有关,而单线程与并行性有关。这两个概念实际上不是一回事。

您仍然可以在单线程上下文中拥有并发代码。事实上,JavaScript,回调地狱的女王,是单线程的。

并发性和并行性有什么区别?

5)您能否也在这个简单的例子中展示RX如何解决“回调地狱问题”。

我对RX一无所知,但通常这个问题可以通过在编程语言中添加对异步计算的本机支持来解决。实现可以变化,包括:异步、生成器、协程和 callcc。

在Python中,我们可以实现前面的循环示例,如下所示:

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

myGen = myLoop()

这不是完整的代码,但这个想法是“yield”暂停我们的for循环,直到有人调用myGen.next()。重要的是,我们仍然可以使用for循环编写代码,而不需要像我们在递归函数中必须做的那样“由内而外”地输出逻辑。loop


答案 2

只需回答这个问题:您能不能也在这个简单的例子中展示RX如何解决“回调地狱问题”?

神奇的是.我们可以在Rx中编写以下代码,用于@hugomg的示例:flatMap

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

这就像您正在编写一些同步FP代码,但实际上您可以通过.Scheduler