如何避免堆积回调或"回调地狱"?

man*_*eak 19 java

我的程序大量使用(可能)异步调用,其中返回值不是立即可用的,因此有很多方法,如下所示:

// A simple callback interface
public interface GetFooCallback
{
    void onResult(Foo foo);
};

// A method that magically retrieves a foo
public void getFoo(long fooID, GetFooCallback callback)
{
    // Retrieve "somehow": local cache, from server etc.
    // Not necessarily this simple.
    Foo foo = ...; 

    callback.onResult(foo);
}
Run Code Online (Sandbox Code Playgroud)

然而,由于有许多事情依赖于这样的最后一次调用,他们开始堆积起来:

// Get foo number 42
fooHandler.getFoo(42, new GetFooCallback()
{
    @Override
    public void onResult(final Foo foo)
    {
        // Foo 42 retrieved, get a related Bar object
        barHandler.getBar(foo.getBarID(), new GetBarCallback()
        {
            @Override
            public void onResult(final Bar bar)
            {
                // Send a new Zorb(foo, bar) to server
                zorbHandler.sendZorbToServer(new Zorb(foo, bar), new ZorbResponseHandler()
                {
                    @Override
                    public void onSuccess()
                    {
                        // Keep dancing
                    }

                    @Override
                    public void onFailure()
                    {
                        // Drop the spoon
                    }
                });
            }
        });
    }
});
Run Code Online (Sandbox Code Playgroud)

这种"有效",但当桩不断增长时,它开始感觉相当顽固,很难跟踪发生的事情.所以,问题是:我如何避免这种堆积?当我用Google搜索"回调地狱"时,很多地方都建议使用RxJava或RxAndroid,但我还没有找到任何一个例子来说明如何将上述例子转换成更简洁的整体.

Zho*_*gYu 9

这是一个有争议的话题,有很多意见; 让我专注于一个特定的解决方案,并试图争论为什么它比回调更好.

解决方案通常被称为未来,承诺等; 关键是异步函数不进行回调; 相反,它返回表示正在进行的异步操作的future/promise.在这里,让我用术语Async来表示异步动作,因为我觉得这是一个更好的名字.

而不是接受回调

    void func1(args, Callback<Foo>)
Run Code Online (Sandbox Code Playgroud)

返回一个异步

    Async<Foo> func2(args)
Run Code Online (Sandbox Code Playgroud)

Async确实包含在完成时接受回调的方法,因此func2可以以类似的方式使用func1

    func1(args, callback);
    // vs
    func2(args).onCompletion( callback )
Run Code Online (Sandbox Code Playgroud)

在这方面,Async至少不比回调解决方案差.

通常,Async不与回调一起使用; 相反,Asyncs被链接了

func2(args)
    .then(foo->func3(...))
    .then(...)
    ...
Run Code Online (Sandbox Code Playgroud)

首先要注意的是,与回调嵌套相反,这是平坦的.


除了审美原因,最重要的是Async什么?有些人认为它与回调基本相同,只是使用另一种语法.但是,有很大的不同.

OOP最大的秘密就是你可以用对象来表示东西......这是一个什么样的分泌?那不是OOP-101吗?但实际上人们常常忘记这一点.

当我们有一个Async对象来表示异步操作时,我们的程序可以轻松地执行操作,例如,通过API传递操作; 取消操作或设置超时; 将多个顺序/并行动作组成一个动作; 这些事情可以在回调解决方案中完成,但是,它只是更加困难和非直观,因为没有程序可以使用的有形对象; 相反,行动的概念只在我们的脑海中.

唯一真正的判断是解决方案是否仅仅是您的应用程序.这是我的异步库,看看它是否有帮助.


Cla*_*diu 0

我不确定这是否有帮助,但这里的设计理念可能会激发解决方案。灵感来自Python的Twisted。最终结果如下所示(解释如下):

Generator<Deferred> theFunc = Deferred.inlineCallbacks(new Generator<Deferred>() {
    public void run() throws InterruptedException {
        Foo foo = (Foo)yield(fooHandler.getFoo(42));
        Bar bar = (Bar)yield(barHandler.getBar(foo.getBarID());
        try {
            yield(zorbHandler.sendZorbToServer(new Zorb(foo, bar));
        } catch (Exception e) {
            // Drop the sooon
            return; 
        }
        // Keep dancing
    }
});

theFunc();
Run Code Online (Sandbox Code Playgroud)

请注意缺少嵌套和回调地狱。


为了解释,我将解释它在 Twisted 中的工作原理。使用 Twisted,您的代码将如下所示:

@defer.inlineCallbacks
def the_func():
    foo = yield fooHandler.getFoo(42)
    bar = yield barHandler.getBar(foo.getBarID())
    try:
        yield zorbHandler.sendZorbToServer(Zorb(foo, bar))
        # Keep dancing
    except Exception:
        # Drop the spoon

the_func()
Run Code Online (Sandbox Code Playgroud)

在此代码片段中,the_func实际上是一个生成延迟对象的生成器。,而不是接受回调,然后使用结果调用回调,而是返回一个 Deferred 对象。可以将回调添加到 Deferred 对象中,当对象触发时调用这些回调。因此,不要看起来像这样:fooHandler.getFoogetFoo

def getFoo(self, value, callback):
    # do some stuff and then in another thread
    doSomeStuffInThread(value, lambda foob: callback(foob))

# Usage:
getFoo(42, myCallback)
Run Code Online (Sandbox Code Playgroud)

它看起来像这样:

def getFoo(self, value):
    deferred = Deferred()
    doSomeStuffInThread(value, lambda foob: deferred.callback(foob))
    return deferred

deferred = getFoo(42)
deferred.addCallback(myCallback)
Run Code Online (Sandbox Code Playgroud)

Deferreds 看起来与CompletableFutures类似,即使不一样(与 相比addCallbackthenApply

然后,defer.inlineCallbacks通过执行如下操作神奇地连接所有回调和所有延迟:

  • 启动生成器,获得第一个 Deferred。
  • 向第一个 Deferred 添加回调,该回调获取结果并将结果发送到生成器。
  • 重复下一个 Deferred 和下一个结果,依此类推,直到生成器耗尽。

也许您可以在 Java 中实现类似的东西 - 请参阅此答案以获取等效的生成器(您必须修改以yield返回值),以及.defer.inlineCallbacks