[java play 2.4.x] Two Promises simultaneously

227 views
Skip to first unread message

Popescu Petre

unread,
Apr 7, 2016, 10:44:41 AM4/7/16
to play-framework
 am working on a Play application that does a lot of communication (retrieving of data) from other 3rd party services. What I am trying to do is "combine" the results of each request.

To be more precise. I have three external WSRequests each encapsulated in a class that has a method that will execute the request, retrieve the JSON String and return a list of objects that are essentially deserialized versions for the JSON. Extremely simplified, it can be reduced to something like this.

Class A {
    function F.Promise<List<ObjA>> call();
}
Class B {
    function F.Promise<List<ObjB>> call(Integer id);
}
Class C {
    function F.Promise<List<ObjC>> call();
}

From my controller, I do calls to objects of A, B and C, do some processing and return a result which is a JSON String of another object (lets call it ObjD).

In my controller, I have something like this:

public F.Promise<Result> compute() {
   A a = new A();
   B b = new B();
   C c = new C();

  List<ObjA> objAList = a.call().get(TIMEOUT);
  List<ObjB> objBList = b.call(objAList.get(0).getId()).get(TIMEOUT);
  List<ObjC> objCList = c.call().get(TIMEOUT);

  // compute something using the three lists. This will create an object objD

  return F.Promise.promise(() -> ok(Json.toJson(objD)));
}

The result for List is dependents on the result from A (so they can't be called simultaneously). Obviusly, i can simply thing like this:

public F.Promise<Result> compute() {
    A a = new A();
    B b = new B();
    C c = new C();

    List<ObjC> objCList = c.call(0.get(TIMEOUT);
    return a.call().map(objAList -> {
       List<ObjB> objBList = b.call(objAList.get(0).getId()).get(TIMEOUT);
       // compute and make objD
       return Json.toJson(objD);
    });
}

What however I do not know is if (and how) to execute the call to A and C simultaneously and start the rest of the processing once both are received. So instead of doing the call to C, waiting for the result and only after that doing the call to A, I want to be able to trigger both A and C calls and once results from both are received to do the computation needed with the results. This will improve performance obviously.

Thanks

Christian Schmitt

unread,
Apr 7, 2016, 12:20:37 PM4/7/16
to play-framework
This should do:


// Helper that the aweful try/catch block will get away
private static <T> T getNow(CompletableFuture<T> future) {
try {
return future.get(); // Will be save if called after CompletableFuture.allOf.thenApply
} catch (Throwable t) {
throw new RuntimeException(t);
}
}

private CompletionStage<String> getFutureMessage(long time, TimeUnit timeUnit) {
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "1");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "2");

CompletableFuture<Void> allDone = CompletableFuture.allOf(f1, f2);
return allDone.thenApply(v -> {
String s1 = getNow(f1);
String s2 = getNow(f2);

return s1 + s2;
});
}

Igmar Palsenberg

unread,
Apr 7, 2016, 12:48:40 PM4/7/16
to play-framework


 am working on a Play application that does a lot of communication (retrieving of data) from other 3rd party services. What I am trying to do is "combine" the results of each request.

To be more precise. I have three external WSRequests each encapsulated in a class that has a method that will execute the request, retrieve the JSON String and return a list of objects that are essentially deserialized versions for the JSON. Extremely simplified, it can be reduced to something like this.

If you can have Promises that return the same type (from a WSRequest for example), you can add all the promises to a list, do a F.Promise.sequence, and then map the result.


Igmar

Popescu Petre

unread,
Apr 8, 2016, 3:38:04 AM4/8/16
to play-framework
That is the thing. They do not return the same type. one is a Promise<List<ObjA>>, the other is Promise<List<ObjC>>

Popescu Petre

unread,
Apr 8, 2016, 3:42:14 AM4/8/16
to play-framework
The problem is that the two promises that I want to run together return different types. So this does not work in my case.

Igmar Palsenberg

unread,
Apr 8, 2016, 3:50:49 AM4/8/16
to play-framework


Op vrijdag 8 april 2016 09:42:14 UTC+2 schreef Popescu Petre:
The problem is that the two promises that I want to run together return different types. So this does not work in my case.

Wrap all in a class, and use a type indicator ? Java doesn't know unions, else that would have been a good option.



Igmar

Popescu Petre

unread,
Apr 8, 2016, 3:56:45 AM4/8/16
to play-framework
That would be a solution, however I would like to avoid type checking at runtime. I find it not very elegant and harder to maintain/change.

Greg Methvin

unread,
Apr 8, 2016, 4:07:51 AM4/8/16
to play-framework
In the map function you can just call .get() on the original promises, ignoring the list. Christian's example is just the CompleteableFuture version of that. It's safe to call get since those promises are already resolved at that point. You just need to use the list promise to tell if all the promises are "done".

--
You received this message because you are subscribed to the Google Groups "play-framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framewor...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/play-framework/f3fe0d0b-4c17-401a-954d-f932041740a3%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Greg Methvin
Senior Software Engineer

Igmar Palsenberg

unread,
Apr 8, 2016, 4:10:52 AM4/8/16
to play-framework

 
In the map function you can just call .get() on the original promises, ignoring the list. Christian's example is just the CompleteableFuture version of that. It's safe to call get since those promises are already resolved at that point. You just need to use the list promise to tell if all the promises are "done".

Have an example of this ? I just used a sequence, but if it can be done easier, always a nice thing.


Igmar 

zhihui di

unread,
Apr 8, 2016, 7:19:54 AM4/8/16
to play-framework

Yes, It works.

Another question:

How to set timeout for the every promises?  I hope I can discard the part result if some promise does not return result in 3 seconds.

Thanks very much.

zhihui di

unread,
Apr 8, 2016, 7:27:40 AM4/8/16
to play-framework
Promise.sequence(promise0,promise1,promise2).map(list -> {

            list.get(0).forEach(map -> {

                
            });

            list.get(1).forEach(map -> {

            });

            list.get(2).forEach(map -> {

            });

            return .....;

        }) ;

Popescu Petre

unread,
Apr 8, 2016, 11:10:39 AM4/8/16
to play-framework
Maybe I wasn't very explicit. So i made a simple sequence diagram to illustrate what I need.
Here, each 'arrow" is a WSRequest/WSResponse that is then treated as a Promise. Something like this:
------CODE---------------------
public F.Promise<List<ObjA>> getData() {
Integer timeout = ConfigurationReaderUtil.getInt(ConfigurationReaderUtil.DEFAULT_TIMEOUT);
if (timeout == null || timeout == 0) {
timeout = 2000;
}

WSRequest request = ws.url(URL);
request.setRequestTimeout(timeout);

Promise<List<String>> responsePromise = request.get().map(response -> {
int status = response.getStatus();
if (status != 200) throw new Error();
JsonNode jsonNodes;
try {
jsonNodes = response.asJson();
} catch (Exception e) {
throw new Error();
}
Iterator<JsonNode> elements = jsonNodes.elements();

List<ObjA> sourcesList = new ArrayList();
while (elements.hasNext()) {
try {
JsonNode jsonNode = elements.next();
String source = Json.fromJson(jsonNode, ObjA.class);
if (source != null && !source.isEmpty()) {
sourcesList.add(source);
}
} catch (Exception e) {
logger.error(loggerUtil.generateGUID(), "Logging original exception", e);
throw new ApplicationException(ApplicationErrorMessage.ERROR_INVALID_FIELD_IN_JSON);
}

}
logger.info(loggerUtil.generateGUID(), "events platform call resultset size:" + sourcesList.size());
logger.debug(loggerUtil.generateGUID(), "sourcesList:" + sourcesList);
return sourcesList;
});
return responsePromise;
}
--------END CODE----------

This is how i currently do it:


What I want is this:

or this:



Please note that each is a Promise of a different data type, so doing a F.Promise.sequence() does not work.
Reply all
Reply to author
Forward
0 new messages