簡體   English   中英

如何避免堆積回調或“回調地獄”?

[英]How to avoid piling up callbacks or “callback hell”?

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

// 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);
}

然而,由於有許多事情依賴於這樣的最后一次調用,他們開始堆積起來:

// 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
                    }
                });
            }
        });
    }
});

這種“有效”,但當樁不斷增長時,它開始感覺相當頑固,很難跟蹤發生的事情。 所以,問題是:我如何避免這種堆積? 當我用Google搜索“回調地獄”時,很多地方都建議使用RxJava或RxAndroid,但我還沒有找到任何一個例子來說明如何將上述例子轉換成更簡潔的整體。

這是一個有爭議的話題,有很多意見; 讓我專注於一個特定的解決方案,並試圖爭論為什么它比回調更好。

解決方案通常被稱為未來,承諾等; 關鍵是異步函數不進行回調; 相反,它返回表示正在進行的異步操作的future / promise。 在這里,讓我使用術語Async來表示異步操作,因為我覺得它是一個更好的名稱。

而不是接受回調

    void func1(args, Callback<Foo>)

返回一個異步

    Async<Foo> func2(args)

Async確實包含在完成時接受回調的方法,因此func2可以以與func1類似的方式使用

    func1(args, callback);
    // vs
    func2(args).onCompletion( callback )

在這方面, Async至少不比回調解決方案差。

通常,Async不與回調一起使用; 相反,Asyncs被鏈接了

func2(args)
    .then(foo->func3(...))
    .then(...)
    ...

首先要注意的是,與回調嵌套相反,這是平坦的。


除了審美原因, Async什么? 有些人認為它與回調基本相同,只是使用另一種語法。 但是,有很大的不同。

OOP最大的秘密就是你可以用對象來表示東西......這是一個什么樣的分泌? 那不是OOP-101嗎? 但實際上人們常常忘記這一點。

當我們有一個Async對象來表示異步操作時,我們的程序可以輕松地執行操作,例如,通過API傳遞操作; 取消操作或設置超時; 將多個順序/並行動作組成一個動作; 這些事情可以在回調解決方案中完成,但是,它只是更加困難和非直觀,因為沒有程序可以使用的有形對象; 相反,行動的概念只在我們的腦海中。

唯一真正的判斷是解決方案是否僅僅是您的應用程序。 這是我的異步庫 ,看看它是否有幫助。

根據具體的使用案例和要求,可能有不同的編程方法或范例可能適合您的特定任務。 這些可能包括各種形式的消息傳遞Actor模型反應式編程 (例如,您已經提到的RxJava),或者通常是某種形式的基於流的編程 甚至可以使用事件總線來交換“事件”(在您的情況下是計算結果)。

但是,它們中的大多數都是基於某些基礎架構構建的 - 尤其是需要對系統進行相應建模的庫。 例如,您的回調可能必須實現特定接口才能獲得有關(異步)結果的通知。 此外,在某些地方,您將需要進行“連線”:您必須指定在結果可用時應調用哪個特定回調 - 即使這可能就像為某個“事件”注冊此回調一樣簡單類型”。


當然,可以手動為此構建必要的基礎架構。 您可以相應地實現您的FooHandler類:

class FooHandler
{
    // Maintain a list of FooCallbacks
    private List<FooCallback> fooCallbacks = new ArrayList<>();
    public void addFooCallback(FooCallback fooCallback)
    {
        fooCallbacks.add(fooCallbacks);
    }

    public void getFoo(long fooID)
    {
        // Retrieve "somehow": local cache, from server etc.
        // Not necessarily this simple.
        Foo foo = ...; 

        publish(foo);
    }

    // Offer a method to broadcast the result to all callbacks
    private void publish(Foo foo)
    {
        for (FooCallback fooCallback : fooCallbacks) 
        {
            fooCallback.onResult(foo);
        }
    }
}

class BarHandler implements FooCallback
{
    // Maintain a list of BarCallbacks, analogously to FooHandler 
    ...

    @Override
    public void onResult(Foo foo)
    {
        Object id = foo.getBarID();
        Bar bar = getFrom(id);

        publish(bar);
    }
}

這樣您就可以使用如下代碼組裝回調結構:

FooHandler fooHandler = new FooHandler();
FooCallback fooCallback = new BarHandler();
fooHandler.addFooCallback(fooCallback);
...
barHandler.add(new MyZorbResponseHandler());

這基本上歸結為將回調作為方法參數傳遞,而是將它們保存在專用列表中。 這至少使布線更容易,更簡潔。 但它仍然會使一般結構相當“僵化”,而不是像一個專門的基礎設施那樣松散耦合,以更抽象的形式模擬這個“聽眾”和信息交換概念。


如果你的主要目標是避免你提到的“堆積”,就代碼的可讀性和可維護性而言(或明顯地:縮進級別),一種方法可能是簡單地將新回調實例的創建提取到實用程序方法中。

雖然這肯定不能替代完整的,復雜的消息傳遞架構,但根據您提供的代碼,這里有一個小例子:

class CompactCallbacks
{
    public static void main(String[] args)
    {
        FooHandler fooHandler = null;
        BarHandler barHandler = null;
        ZorbHandler zorbHandler = null;

        fooHandler.getFoo(42, 
            createFooCallback(barHandler, zorbHandler));
    }

    private static GetFooCallback createFooCallback(
        BarHandler barHandler, ZorbHandler zorbHandler)
    {
        return foo -> barHandler.getBar(
            foo.getBarID(), createGetBarCallback(zorbHandler, foo));
    }

    private static GetBarCallback createGetBarCallback(
        ZorbHandler zorbHandler, Foo foo)
    {
        return bar -> zorbHandler.sendZorbToServer(
            new Zorb(foo, bar), createZorbResponseHandler());
    }

    private static ZorbResponseHandler createZorbResponseHandler()
    {
        return new ZorbResponseHandler()
        {
            @Override
            public void onSuccess()
            {
                // Keep dancing
            }

            @Override
            public void onFailure()
            {
                // Drop the spoon
            }
        };
    }
}

//============================================================================
// Only dummy classes below this line
class FooHandler
{
    public void getFoo(int i, GetFooCallback getFooCallback)
    {
    }
}
interface GetFooCallback
{
    public void onResult(final Foo foo);
}
class Foo
{
    public int getBarID()
    {
        return 0;
    }
}
class BarHandler
{
    public void getBar(int i, GetBarCallback getBarCallback)
    {
    }
}
interface GetBarCallback
{
    public void onResult(final Bar bar);
}
class Bar
{
}
class ZorbHandler
{
    public void getZorb(int i, GetZorbCallback getZorbCallback)
    {
    }
    public void sendZorbToServer(Zorb zorb,
        ZorbResponseHandler zorbResponseHandler)
    {
    }
}
interface GetZorbCallback
{
    public void onResult(final Zorb Zorb);
}
class Zorb
{
    public Zorb(Foo foo, Bar bar)
    {
    }
    public int getBarID()
    {
        return 0;
    }
}
interface ZorbResponseHandler
{
    void onSuccess();
    void onFailure();
}

我不確定這是否會有所幫助,但這是一個可以激發解決方案的設計理念。 靈感來自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();

注意缺乏嵌套和回調地獄。


為了解釋,我將解釋它如何在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()

在這個片段中, the_func實際上是一個生成 Deferred對象的生成器 fooHandler.getFoo ,而不是接受回調,然后用結果調用回調,而是返回一個Deferred對象。 可以將回調添加到Deferred對象,該對象在對象觸發時調用。 因此,而不是getFoo看起來像這樣:

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

# Usage:
getFoo(42, myCallback)

它看起來像這樣:

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

deferred = getFoo(42)
deferred.addCallback(myCallback)

延遲看起來與CompletableFutures相似(如果不相同)(將addCallbackthenApply進行比較)。

然后, defer.inlineCallbacks通過執行以下操作,神奇地連接所有回調和所有延遲:

  • 啟動生成器,獲得第一個Deferred。
  • 將回調添加到第一個Deferred,它將獲取結果並將結果發送到生成器。
  • 重復下一個Deferred和下一個結果,依此類推,直到生成器耗盡。

也許您可以在Java中實現類似的東西 - 請參閱此答案以獲取生成器等效項(您必須修改yield以返回值),以及用於defer.inlineCallbacks實現的Twisted源代碼

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM