I was reading some http webservice API from playframework, link below.
I am not able to understand how this nested work with the flatmap.
Can someone give me some hints how to crack down this big chunk of functions call.
from http://www.playframework.com/documentation/2.2.x/JavaWS
Composing results
If you want to make multiple calls in sequence,
this can be achieved using flatMap:
public static Promise<Result> index() {
final Promise<Result> resultPromise = WS.url(feedUrl).get().flatMap(
new Function<WS.Response, Promise<Result>>() {
public Promise<Result> apply(WS.Response response) {
return WS.url(response.asJson().findPath("commentsUrl").asText()).get().map(
new Function<WS.Response, Result>() {
public Result apply(WS.Response response) {
return ok("Number of comments: " + response.asJson().findPath("count").asInt());
}
}
);
}
}
);
return resultPromise;
}
flatMap
and map
are common Scala (or more generally, functional programming) functions. They both accept a function as a parameter. In order to translate the Play WS API into Java (and pretty much everything else), Scala's function type needed to be re-implemented in Java, so that you can take full advantage of the WS library. It's being done here in a similar way the Scala compiler does it. Function<A,B>
is an abstract type that requires an apply
method. The parameter(s) of apply
are the parameters of the function, and the return type of apply
is that of the function.
If you have this function in Java:
public String int2String(Integer integer) {
return integer.toString();
}
It would be equivalent to this:
new Function<Integer, String>() {
public String apply(Integer integer) {
return integer.toString();
}
}
Let's start simpler with the case of just one WS call. WS.url(...).get()
returns a Promise<WS.Response>
. Promise
is a container class for a promised value. In order to handle the value it contains (or will eventually contain), we need to use the map
function. For a Promise<WS.Response>
, map
will accept a Function<WS.Response, T>
as a parameter, where T
is the type you want to map the response to.
As an example, let's define a Function
that will just return the body of the WS.Response
in a Play HTTP Result
:
Function<WS.Response, Result> echo = new Function<WS.Response, Result>() {
public Result apply(WS.Response response) {
return ok(response.asText());
}
}
Now let's use this Function
in a WS call in a controller:
public static Promise<Result> index() {
final Promise<Result> resultPromise = WS.url("http://google.com").get().map(echo);
return resultPromise;
}
Once the Promise
has been fulfilled, the echo
function defined earlier will be executed inside map
, returning the Promise<Result>
. The two previous blocks of code could also be written like this (combined into one with an anonymous function):
public static Promise<Result> index() {
final Promise<Result> resultPromise = WS.url("http://google.com").get().map(
new Function<WS.Response, Result>() {
public Result apply(WS.Response response) {
return ok(response.asText());
}
}
);
return resultPromise;
}
As a crude example, let's say we need to make two WS calls. The second WS call will depend on the first. Perhaps the first call will give us some URL we will use to make the second WS call.
This is where flatMap
comes into picture. We will need two functions to accomplish this task. The first function is what is passed to flatMap
, which will be executed when the first WS.Response
is received. This first function will use the first response to make the second WS call, which returns another Promise<WS.Response>
that must be mapped to get our final result. So we map
the second result with a second function that translates the WS.Response
into our Result
.
So what happened? If we used map
instead of flatMap
in both instances, the chain of events would go something like this:
The first get()
returned a Promise<WS.Response>
, then we map
the contained WS.Response
to a Promise<Result>
. That, however, would leave us with a Promise<Promise<WS.Response>>
, which is not very desirable. Using flatMap
in the outer function instead will flatten the Promise
s into a single Promise<Result>
. Simiarly, if you were doing 3 or more nested calls, you would map
each result to an inner function, and have just one flatMap
at the outer level to flatten everything at the end.
This all of course looks much more beautiful in Scala.