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

有人可以给出一个清晰的定义和一个简单的例子,解释什么是不知道JavaScript和node.js的人的“回拨地狱”?

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

为什么会发生?

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

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

我在Coursera参加了Reactive Course,Erik Meijer在他的一次演讲中表示,RX解决了“回拨地狱”的问题。 我问在Coursera论坛上什么是“回拨地狱”,但我没有明确的答案。

通过一个简单的例子解释“回调地狱”之后,您是否也可以展示RX如何解决这个简单例子中的“回调地狱问题”?


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)什么时候(在什么样的设置下)发生“回拨地狱问题”?

当你的代码中有很多回调函数时! 在你的代码中使用它们的难度越来越大,当你需要做循环,try-catch块和类似的东西时,它会变得特别糟糕。

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

//we would like to write
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!

JavaScript标签问如何解决这个问题的次数应该证明它是多么令人困惑:)

3)为什么会发生?

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

4)还是可以在单线程应用程序中发生“回调地狱”?

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

您仍然可以在单个线程环境中使用并发代码。 实际上,JavaScript是回调地狱的女王,它是单线程的。

并发vs并行 - 有什么区别?

5)你能否也请说明RX如何解决这个简单例子中的“回调地狱问题”。

我对RX没有什么特别的了解,但通常通过在编程语言中添加对异步计算的本地支持来解决此问题。 这可以非常多,可以有不同的名称(异步,生成器,协程,callcc,...)。 对于一个糟糕的具体例子,在Python中,我们可以使用生成器来编写循环示例,其中包含以下内容:

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

myGen = myLoop()

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


只需回答这个问题:你能否也请说明RX如何解决这个简单例子中的“回调地狱问题”?

神奇的是flatMap 。 我们可以在Rx中为@ hugomg的例子编写下面的代码:

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

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


回调地狱是在异步代码中使用函数回调变得模糊或难以遵循的任何代码。 一般来说,当间接程度不止一个级别时,使用回调的代码可能变得更难以遵循,难以重构并且更难以测试。 由于传递了多层函数文字,代码异味是多级缩进。

这经常发生在行为有依赖关系时,也就是说,在B必须发生在C之前,必须发生A事件。然后你得到这样的代码:

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

如果您的代码中存在很多行为依赖关系,它可能会很快就会变得麻烦。 特别是如果它分支...

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 {
          ...
        }
    }
});

这不会。 我们如何能够以确定的顺序执行异步代码,而不必传递所有这些回调?

RX是'被动扩展'的缩写。 我没有使用它,但谷歌表示这是一个基于事件的框架,这是有道理的。 事件是使代码顺序执行而不产生脆性耦合的常见模式 。 你可以让C收听'bFinished'事件,这个事件只发生在B被称为'aFinished'之后。 然后,您可以轻松地添加额外的步骤或扩展这种行为,并且可以轻松地测试代码的执行顺序,只需在测试用例中广播事件即可。

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

上一篇: What is "callback hell" and how and why RX solves it?

下一篇: Difference between a "coroutine" and a "thread"?