curioustechizen curioustechizen - 5 months ago 23
Java Question

Chain a series of asynchronous calls

I have a series of asynchronous operations

private void doSomething(){
get("/something", new Callback(){
void onComplete(String data){
updateUi(something, data);
doSomethingElse();
}
});
}

private void doSomethingElse(){
get("/something/else", new Callback(){
void onComplete(String data){
updateUi(somethingElse, data);
doYetAnotherThing();
}
});
}

private void doYetAnotherThing(){
get("/yet/another/thing", new Callback(){
void onComplete(String data){
updateUi(yetAnotherThing, data);
allDone();
}
});
}


This suffers from few problems:


  1. Cannot reuse any of the callbacks elsewhere since each is intrinsically tied to the "next step"

  2. Re-ordering operations or inserting another operation is non-intuitive and involves jumping all over the place.



I have looked at the following options to mitigate this:


  1. ExecuterService#invokeAll
    - I don't see how this solution can be used without blocking.

  2. RxJava - I would prefer to avoid such a paradigm shift in my application if I can!

  3. Guava's
    ListenableFutures
    and its
    transform
    method. I saw this referred to in few places around the interwebs nut I honestly don't see how this would solve my problem.



So, the question is: What would be a good pattern to chain a series of asynchronous calls in Java? Looking for a solution that works with Java 7 since I need this for an Android app.

Answer

There certainly is some guessing involved, regarding the actual intention and use-case where you encountered this problem. Additionally, it is not entirely clear what something, somethingElse and yetAnotherThing are (where they come from and where they should go).

However, based on the information that you provided, and as an addition to (or rather extension or generalization of) the answer by slartidan: The difference between these dummy calls that you sketched there seem to be

  • The String argument that is passed to the get method
  • The Callback that is called
  • Which method is executed next

You could factor out these parts: The String argument and the Callback could be passed as parameters to a general method that creates a Callable. The sequence of the calls could simply be defined by placing these Callable objects into a list, in the appropriate order, and execute them all with a single threaded executor service.

As you can see in the main method of this example, the sequence of calls can then be configured rather easily:

import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class ChainedAsyncTest {

    public static void main(String[] args) throws InterruptedException {
        ChainedAsyncTest t = new ChainedAsyncTest();
        ExecutorService e = Executors.newFixedThreadPool(1);
        e.invokeAll(Arrays.asList(
            t.call("/something", t.somethingCallback),
            t.call("/something/else", t.somethingElseCallback),
            t.call("/yet/another/thing", t.yetAnotherThingCallback),
            t.allDone()));
    }

    private Callback somethingCallback = new Callback() {
        @Override
        public void onComplete(String data) {
            updateUi("something", data);
        }
    };

    private Callback somethingElseCallback = new Callback() {
        @Override
        public void onComplete(String data) {
            updateUi("somethingElse", data);
        }
    };

    private Callback yetAnotherThingCallback = new Callback() {
        @Override
        public void onComplete(String data) {
            updateUi("yetAnotherThing", data);
        }
    };

    private Callable<Void> call(
        final String key, final Callback callback) {
        return new Callable<Void>() {
            @Override
            public Void call() {
                get(key, callback);
                return null;
            }
        };
    }

    private Callable<Void> allDone() {
        return new Callable<Void>() {
            @Override
            public Void call() {
                System.out.println("allDone");
                return null;
            }
        };
    }



    interface Callback
    {
        void onComplete(String data);
    }
    private void get(String string, Callback callback) {
        System.out.println("Get "+string);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        callback.onComplete("result of "+string);
    }
    private void updateUi(String string, String data) {
        System.out.println("UpdateUI of "+string+" with "+data);
    }

}

(The example uses invokeAll, which blocks until all tasks have been executed. This could be solved differently to be really non-blocking at the call site. The main idea is to create a list of the tasks, which are all created by the same method call)

Comments