Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decouple GWT Asynchronous callbacks

I have the following problem: I am trying to model a process using GWT, where i have a couple of views with a couple of submit buttons. And pressing button1 will create a server interaction and if everything was ok, the next view will be loaded. My problem is now that I get really nasty spaghetti code (just very highlevel to show you what i mean):

onClick {
    AsyncCallback {
       onSuccess {

           load new view with another clickhandler and an asynccallback

       }
    }
}

Is there some way to create some kind of abstraction or something? Maybe a state pattern? How? Thanks a lot!

like image 811
Bo Chen Avatar asked Jul 11 '12 08:07

Bo Chen


2 Answers

This is actually a very good question - and probably one without a definitive answer. It's a problem that applies to many frameworks, not just GWT, so I like your idea to look at this with some simplified code. I'll make this a little bit longer, to show what even just 4 very simple callbacks look like:

Nested callbacks

alice.call("a", new Callback() {

  @Override
  public void onSuccess() {
    bob.call("b", new Callback() {

      @Override
      public void onSuccess() {
        charlie.call("c", new Callback() {

          @Override
          public void onSuccess() {
            daisy.call("d", new Callback() {

              @Override
              public void onSuccess() {
                // finished
              }

            });
          }

        });
      }

    });
  }

});

Named callbacks

You can use your IDE to refactor this easily into named callbacks (hint: Please read the callbacks from bottom to top!):

final Callback daisyCallback = new Callback() {

  @Override
  public void onSuccess() {
    // finished
  }
};

final Callback charlieCallback = new Callback() {

  @Override
  public void onSuccess() {
    daisy.call("d", daisyCallback);
  }
};

final Callback bobCallback = new Callback() {

  @Override
  public void onSuccess() {
    charlie.call("c", charlieCallback);
  }
};

final Callback aliceCallback = new Callback() {

  @Override
  public void onSuccess() {
    bob.call("b", bobCallback);
  }
};

alice.call("a", aliceCallback);
  • Problem: The control flow is not so immediately obvious anymore.
  • Still, an IDE can help by using "Search References" (Ctrl-G in Eclipse) or something similar.

Event Bus (or Observer/Publish-Subscribe pattern)

This is how the same calls look like with an event bus:

alice.call("a", new Callback() {

  @Override
  public void onSuccess() {
    bus.fireEvent(BusEvent.ALICE_SUCCESSFUL_EVENT);
  }
});

bus.addEventListener(BusEvent.ALICE_SUCCESSFUL_EVENT, new BusEventListener() {

  @Override
  public void onEvent(final BusEvent busEvent) {
    bob.call("b", new Callback() {

      @Override
      public void onSuccess() {
        bus.fireEvent(BusEvent.BOB_SUCCESSFUL_EVENT);
      }
    });

  }
});

bus.addEventListener(BusEvent.BOB_SUCCESSFUL_EVENT, new BusEventListener() {

  @Override
  public void onEvent(final BusEvent busEvent) {
    charlie.call("c", new Callback() {

      @Override
      public void onSuccess() {
        bus.fireEvent(BusEvent.CHARLIE_SUCCESSFUL_EVENT);
      }
    });

  }
});

bus.addEventListener(BusEvent.CHARLIE_SUCCESSFUL_EVENT, new BusEventListener() {

  @Override
  public void onEvent(final BusEvent busEvent) {
    daisy.call("d", new Callback() {

      @Override
      public void onSuccess() {
        bus.fireEvent(BusEvent.DAISY_SUCCESSFUL_EVENT);
      }
    });

  }
});

bus.addEventListener(BusEvent.DAISY_SUCCESSFUL_EVENT, new BusEventListener() {

  @Override
  public void onEvent(final BusEvent busEvent) {
    // finished
  }
});
  • Under the right circumstances (when it's very clear what each event means, and if you don't have too many), this pattern can make things very nice and clear.
  • But in other cases, it can make the control flow more confusing (and you easily get twice the lines of code).
  • It's harder to use your IDE to find out about the control flow.
  • The GWT History mechanism is a very positive example for where to use this technique reasonably.

Divide and Conquer

In my experience, it's often a good idea to "divide and conquer" by mixing nesting and named callbacks:

final Callback charlieCallback = new Callback() {

  @Override
  public void onSuccess() {

    daisy.call("d", new Callback() {

      @Override
      public void onSuccess() {
        // finished
      }
    });
  }
};

alice.call("a", new Callback() {

  @Override
  public void onSuccess() {

    bob.call("b", new Callback() {

      @Override
      public void onSuccess() {

        charlie.call("c", charlieCallback);
      }
    });
  }
});

Depending on the situation, two nested callbacks are often still readable, and they reduce jumping around between methods when reading the code by 50%.

(I created a pastebin of my examples here, if you like to play around with them: http://pastebin.com/yNc9Cqtb)

like image 189
Chris Lercher Avatar answered Oct 16 '22 10:10

Chris Lercher


Spaghetti code is a tricky problem in GWT as it is in Javascript, where much of your code is structured around asynchronous callbacks.

Some of the techniques to deal with it that are described in the answers to this question could apply.

like image 26
funkybro Avatar answered Oct 16 '22 08:10

funkybro